<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>adore🍊</title>
  
  
  <link href="https://blog.adoreorg.cn/atom.xml" rel="self"/>
  
  <link href="https://blog.adoreorg.cn/"/>
  <updated>2025-10-22T14:30:10.672Z</updated>
  <id>https://blog.adoreorg.cn/</id>
  
  <author>
    <name>adore🍊</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>数论算法：最大公约数与素数相关算法详解</title>
    <link href="https://blog.adoreorg.cn/posts/eb83kdf3.html"/>
    <id>https://blog.adoreorg.cn/posts/eb83kdf3.html</id>
    <published>2025-10-21T16:00:00.000Z</published>
    <updated>2025-10-22T14:30:10.672Z</updated>
    
    <content type="html"><![CDATA[<h1>数论算法：最大公约数与素数相关算法详解</h1><h2 id="🎯-学习目标">🎯 学习目标</h2><p>通过本教程，你将掌握以下数论核心算法的Java实现：</p><ul><li>✅ 最大公约数（GCD）的计算方法</li><li>✅ 最小公倍数（LCM）的计算方法</li><li>✅ 素数的判断算法</li><li>✅ 素因数分解算法</li><li>✅ 埃拉托斯特尼筛法生成素数</li><li>✅ 欧拉函数计算</li><li>✅ 互质数判断</li><li>✅ 扩展欧几里得算法（贝祖定理）</li></ul><h2 id="📑-目录">📑 目录</h2><ul><li><a href="#-%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5">📚 核心概念</a></li><li><a href="#-%E6%9C%80%E5%A4%A7%E5%85%AC%E7%BA%A6%E6%95%B0gcd">🔍 最大公约数（GCD）</a></li><li><a href="#-%E6%9C%80%E5%B0%8F%E5%85%AC%E5%80%8D%E6%95%B0lcm">🔍 最小公倍数（LCM）</a></li><li><a href="#-%E7%B4%A0%E6%95%B0%E5%88%A4%E6%96%AD">🔍 素数判断</a></li><li><a href="#-%E7%B4%A0%E5%9B%A0%E6%95%B0%E5%88%86%E8%A7%A3">🔍 素因数分解</a></li><li><a href="#-%E5%9F%83%E6%8B%89%E6%89%98%E6%96%AF%E7%89%B9%E5%B0%BC%E7%AD%9B%E6%B3%95">🔍 埃拉托斯特尼筛法</a></li><li><a href="#-%E6%AC%A7%E6%8B%89%E5%87%BD%E6%95%B0">🔍 欧拉函数</a></li><li><a href="#-%E4%BA%92%E8%B4%A8%E6%95%B0%E5%88%A4%E6%96%AD">🔍 互质数判断</a></li><li><a href="#-%E6%89%A9%E5%B1%95%E6%AC%A7%E5%87%A0%E9%87%8C%E5%BE%97%E7%AE%97%E6%B3%95">🔍 扩展欧几里得算法</a></li><li><a href="#-%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90">⚡ 性能分析</a></li><li><a href="#-%E6%80%BB%E7%BB%93%E4%B8%8E%E5%BA%94%E7%94%A8">💡 总结与应用</a></li></ul><h2 id="📚-核心概念">📚 核心概念</h2><p>数论是数学的一个分支，主要研究整数的性质。本教程将介绍数论中最基础且常用的算法，这些算法在密码学、计算机科学和工程领域都有广泛的应用。</p><p><strong>核心概念定义：</strong></p><ul><li><strong>最大公约数（GCD）</strong>：两个或多个整数共有约数中最大的一个</li><li><strong>最小公倍数（LCM）</strong>：两个或多个整数公有的倍数中最小的一个</li><li><strong>素数</strong>：大于1的自然数，除了1和它本身外，不能被其他自然数整除的数</li><li><strong>素因数</strong>：一个数的素数因数</li><li><strong>互质数</strong>：公约数只有1的两个数</li><li><strong>欧拉函数</strong>：小于n且与n互质的正整数的个数</li></ul><h2 id="🔍-最大公约数（GCD）">🔍 最大公约数（GCD）</h2><h3 id="🎨-核心思想">🎨 核心思想</h3><p>最大公约数（Greatest Common Divisor, GCD）是指两个或多个整数共有约数中最大的一个。计算最大公约数最常用的算法是<strong>欧几里得算法</strong>（又称辗转相除法），其核心思想基于以下性质：</p><p>如果 a 和 b 是两个正整数，那么 gcd(a, b) = gcd(b, a mod b)，当 b = 0 时，gcd(a, 0) = a。</p><h3 id="💻-Java实现">💻 Java实现</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 使用欧几里得算法计算两个数的最大公约数（GCD）</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> a 第一个整数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> b 第二个整数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 两个数的最大公约数</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">gcd</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span> &#123;</span><br><span class="line">    <span class="comment">// 取绝对值以处理负数</span></span><br><span class="line">    a = Math.abs(a);</span><br><span class="line">    b = Math.abs(b);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 欧几里得算法核心逻辑</span></span><br><span class="line">    <span class="keyword">while</span> (b != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">temp</span> <span class="operator">=</span> b;</span><br><span class="line">        b = a % b;</span><br><span class="line">        a = temp;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> a;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="📝-算法分析">📝 算法分析</h3><ul><li><strong>时间复杂度</strong>：O(log(min(a, b)))，因为每次迭代，较大的数至少减少一半</li><li><strong>空间复杂度</strong>：O(1)，只使用了常数额外空间</li><li><strong>应用场景</strong>：分数简化、求解线性同余方程、密码学算法等</li></ul><h2 id="🔍-最小公倍数（LCM）">🔍 最小公倍数（LCM）</h2><h3 id="🎨-核心思想-2">🎨 核心思想</h3><p>最小公倍数（Least Common Multiple, LCM）可以通过最大公约数计算，公式为：</p><p>LCM(a, b) = |a × b| / GCD(a, b)</p><p>这个公式基于以下原理：两个数的乘积等于它们的最大公约数和最小公倍数的乘积。</p><h3 id="💻-Java实现-2">💻 Java实现</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 计算两个数的最小公倍数（LCM）</span></span><br><span class="line"><span class="comment"> * LCM(a,b) = |a*b| / GCD(a,b)</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> a 第一个整数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> b 第二个整数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 两个数的最小公倍数</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">lcm</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (a == <span class="number">0</span> || b == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>; <span class="comment">// 0和任何数的最小公倍数为0</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> Math.abs(a * b) / gcd(a, b);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="🔍-素数判断">🔍 素数判断</h2><h3 id="🎨-核心思想-3">🎨 核心思想</h3><p>判断一个数是否为素数，最直观的方法是检查从2到√n的每个整数是否能整除n。为了优化，可以先检查数是否能被2或3整除，然后只检查形如6k±1的数（因为所有素数大于3后，都可以表示为6k±1的形式）。</p><h3 id="💻-Java实现-3">💻 Java实现</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 判断一个数是否为素数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> n 要判断的整数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 如果n是素数返回true，否则返回false</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">boolean</span> <span class="title function_">isPrime</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>; <span class="comment">// 0和1不是素数</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">3</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>; <span class="comment">// 2和3是素数</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (n % <span class="number">2</span> == <span class="number">0</span> || n % <span class="number">3</span> == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>; <span class="comment">// 能被2或3整除的数不是素数</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 检查从5开始到sqrt(n)的所有数，跳过偶数和3的倍数</span></span><br><span class="line">    <span class="comment">// 只需要检查形式为6k±1的数</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">5</span>; i * i &lt;= n; i += <span class="number">6</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (n % i == <span class="number">0</span> || n % (i + <span class="number">2</span>) == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="📝-算法分析-2">📝 算法分析</h3><ul><li><strong>时间复杂度</strong>：O(√n)，但由于优化，实际运行时间比简单的O(√n)算法更快</li><li><strong>空间复杂度</strong>：O(1)</li><li><strong>优化点</strong>：跳过了很多不必要的检查，尤其是偶数和3的倍数</li></ul><h2 id="🔍-素因数分解">🔍 素因数分解</h2><h3 id="🎨-核心思想-4">🎨 核心思想</h3><p>素因数分解是将一个合数分解成若干个素数的乘积的过程。算法思想是从最小的素数2开始，不断尝试将输入数除以当前素数，如果能整除，则将该素数添加到结果列表中，并继续除以该素数，直到不能整除为止。然后依次尝试更大的素数。</p><h3 id="💻-Java实现-4">💻 Java实现</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取一个数的所有素因数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> n 要分解的整数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> n的素因数列表</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> List&lt;Integer&gt; <span class="title function_">primeFactors</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    List&lt;Integer&gt; factors = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 处理负数情况</span></span><br><span class="line">    <span class="keyword">if</span> (n &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        n = Math.abs(n);</span><br><span class="line">        factors.add(-<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 分解2的因数</span></span><br><span class="line">    <span class="keyword">while</span> (n % <span class="number">2</span> == <span class="number">0</span>) &#123;</span><br><span class="line">        factors.add(<span class="number">2</span>);</span><br><span class="line">        n = n / <span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 分解奇数因数</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">3</span>; i * i &lt;= n; i += <span class="number">2</span>) &#123;</span><br><span class="line">        <span class="keyword">while</span> (n % i == <span class="number">0</span>) &#123;</span><br><span class="line">            factors.add(i);</span><br><span class="line">            n = n / i;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 如果最后剩下的数大于2，它本身就是一个素因数</span></span><br><span class="line">    <span class="keyword">if</span> (n &gt; <span class="number">2</span>) &#123;</span><br><span class="line">        factors.add(n);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> factors;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="📝-算法分析-3">📝 算法分析</h3><ul><li><strong>时间复杂度</strong>：O(√n)，最坏情况下需要检查到√n</li><li><strong>空间复杂度</strong>：O(log n)，存储素因数的列表大小</li></ul><h2 id="🔍-埃拉托斯特尼筛法">🔍 埃拉托斯特尼筛法</h2><h3 id="🎨-核心思想-5">🎨 核心思想</h3><p>埃拉托斯特尼筛法（Sieve of Eratosthenes）是一种高效的生成素数的算法。其核心思想是：</p><ol><li>创建一个从2到n的数字表</li><li>从2开始，将每个素数的所有倍数标记为非素数</li><li>找到下一个未被标记的数，重复步骤2</li><li>当处理到√n时，所有未被标记的数都是素数</li></ol><h3 id="💻-Java实现-5">💻 Java实现</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 使用埃拉托斯特尼筛法生成小于等于n的所有素数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> n 上限值</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 小于等于n的素数列表</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> List&lt;Integer&gt; <span class="title function_">sieveOfEratosthenes</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    List&lt;Integer&gt; primes = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (n &lt; <span class="number">2</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> primes; <span class="comment">// 小于2的数没有素数</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 创建布尔数组标记是否为素数</span></span><br><span class="line">    <span class="type">boolean</span>[] isPrime = <span class="keyword">new</span> <span class="title class_">boolean</span>[n + <span class="number">1</span>];</span><br><span class="line">    Arrays.fill(isPrime, <span class="literal">true</span>); <span class="comment">// 初始假设所有数都是素数</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 0和1不是素数</span></span><br><span class="line">    isPrime[<span class="number">0</span>] = isPrime[<span class="number">1</span>] = <span class="literal">false</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 筛选过程</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">2</span>; i * i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (isPrime[i]) &#123; <span class="comment">// 如果i是素数</span></span><br><span class="line">            <span class="comment">// 标记i的所有倍数为非素数</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> i * i; j &lt;= n; j += i) &#123;</span><br><span class="line">                isPrime[j] = <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 收集所有素数</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (isPrime[i]) &#123;</span><br><span class="line">            primes.add(i);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> primes;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="📝-算法分析-4">📝 算法分析</h3><ul><li><strong>时间复杂度</strong>：O(n log log n)，这是一个非常高效的算法</li><li><strong>空间复杂度</strong>：O(n)，需要一个大小为n+1的布尔数组</li><li><strong>优点</strong>：对于生成范围内的所有素数，这是已知最高效的算法之一</li></ul><h2 id="🔍-欧拉函数">🔍 欧拉函数</h2><h3 id="🎨-核心思想-6">🎨 核心思想</h3><p>欧拉函数φ(n)表示小于n且与n互质的正整数的个数。计算欧拉函数的公式基于数的素因数分解：</p><p>如果n的素因数分解为 n = p₁^k₁ × p₂^k₂ × … × p_m^k_m，那么：</p><p>φ(n) = n × (1 - 1/p₁) × (1 - 1/p₂) × … × (1 - 1/p_m)</p><h3 id="💻-Java实现-6">💻 Java实现</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 计算一个数的欧拉函数值（小于n且与n互质的正整数的个数）</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> n 输入整数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> n的欧拉函数值</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">eulerTotient</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">result</span> <span class="operator">=</span> n;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 分解质因数并应用欧拉函数公式</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">p</span> <span class="operator">=</span> <span class="number">2</span>; p * p &lt;= n; p++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (n % p == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// p是素因数</span></span><br><span class="line">            <span class="keyword">while</span> (n % p == <span class="number">0</span>) &#123;</span><br><span class="line">                n = n / p;</span><br><span class="line">            &#125;</span><br><span class="line">            result -= result / p;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 如果n本身是素数</span></span><br><span class="line">    <span class="keyword">if</span> (n &gt; <span class="number">1</span>) &#123;</span><br><span class="line">        result -= result / n;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="📝-算法分析-5">📝 算法分析</h3><ul><li><strong>时间复杂度</strong>：O(√n)</li><li><strong>空间复杂度</strong>：O(1)</li><li><strong>应用</strong>：密码学中的RSA算法、模运算中的逆元计算等</li></ul><h2 id="🔍-互质数判断">🔍 互质数判断</h2><h3 id="🎨-核心思想-7">🎨 核心思想</h3><p>两个数互质当且仅当它们的最大公约数为1。因此，可以通过计算两个数的GCD来判断它们是否互质。</p><h3 id="💻-Java实现-7">💻 Java实现</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 判断两个数是否互质</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> a 第一个整数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> b 第二个整数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 如果a和b互质返回true，否则返回false</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">boolean</span> <span class="title function_">areCoprime</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> gcd(a, b) == <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="📝-算法分析-6">📝 算法分析</h3><ul><li><strong>时间复杂度</strong>：与GCD算法相同，为O(log(min(a, b)))</li><li><strong>空间复杂度</strong>：O(1)</li></ul><h2 id="🔍-扩展欧几里得算法">🔍 扩展欧几里得算法</h2><h3 id="🎨-核心思想-8">🎨 核心思想</h3><p>扩展欧几里得算法是欧几里得算法的扩展，它不仅计算两个数的GCD，还能找到整数x和y，使得 ax + by = gcd(a, b)（贝祖定理）。这个算法在求解线性同余方程和计算模逆元等方面有重要应用。</p><h3 id="💻-Java实现-8">💻 Java实现</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 贝祖定理：对于任何整数a和b，存在整数x和y，使得ax + by = gcd(a,b)</span></span><br><span class="line"><span class="comment"> * 使用扩展欧几里得算法求解贝祖系数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> a 第一个整数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> b 第二个整数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 包含gcd(a,b)和系数x,y的数组 [gcd, x, y]</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span>[] extendedGcd(<span class="type">int</span> a, <span class="type">int</span> b) &#123;</span><br><span class="line">    <span class="keyword">if</span> (b == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 基本情况：a * 1 + 0 * 0 = a</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[]&#123;a, <span class="number">1</span>, <span class="number">0</span>&#125;;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span>[] result = extendedGcd(b, a % b);</span><br><span class="line">    <span class="type">int</span> <span class="variable">gcd</span> <span class="operator">=</span> result[<span class="number">0</span>];</span><br><span class="line">    <span class="type">int</span> <span class="variable">x1</span> <span class="operator">=</span> result[<span class="number">1</span>];</span><br><span class="line">    <span class="type">int</span> <span class="variable">y1</span> <span class="operator">=</span> result[<span class="number">2</span>];</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 推导当前系数</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> y1;</span><br><span class="line">    <span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> x1 - (a / b) * y1;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[]&#123;gcd, x, y&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="📝-算法分析-7">📝 算法分析</h3><ul><li><strong>时间复杂度</strong>：O(log(min(a, b)))</li><li><strong>空间复杂度</strong>：O(log(min(a, b)))，由于递归调用栈</li><li><strong>应用</strong>：求解线性同余方程、计算模逆元、密码学中的密钥生成等</li></ul><h2 id="⚡-性能分析">⚡ 性能分析</h2><table><thead><tr><th>算法</th><th>时间复杂度</th><th>空间复杂度</th><th>适用场景</th></tr></thead><tbody><tr><td>最大公约数</td><td>O(log(min(a, b)))</td><td>O(1)</td><td>分数简化、模运算</td></tr><tr><td>最小公倍数</td><td>O(log(min(a, b)))</td><td>O(1)</td><td>分数运算、周期计算</td></tr><tr><td>素数判断</td><td>O(√n)</td><td>O(1)</td><td>快速判断单个素数</td></tr><tr><td>素因数分解</td><td>O(√n)</td><td>O(log n)</td><td>数论分析、密码学</td></tr><tr><td>埃拉托斯特尼筛法</td><td>O(n log log n)</td><td>O(n)</td><td>生成范围内所有素数</td></tr><tr><td>欧拉函数</td><td>O(√n)</td><td>O(1)</td><td>模运算、密码学</td></tr><tr><td>互质数判断</td><td>O(log(min(a, b)))</td><td>O(1)</td><td>密码学、数论分析</td></tr><tr><td>扩展欧几里得算法</td><td>O(log(min(a, b)))</td><td>O(log(min(a, b)))</td><td>求解线性同余方程</td></tr></tbody></table><h2 id="💡-总结与应用">💡 总结与应用</h2><h3 id="🎯-主要应用领域">🎯 主要应用领域</h3><ol><li><strong>密码学</strong>：RSA算法、椭圆曲线加密等都依赖于数论算法</li><li><strong>计算机科学</strong>：哈希函数、随机数生成、数据压缩等</li><li><strong>工程应用</strong>：信号处理、编码理论、计算机图形学等</li><li><strong>数学研究</strong>：解决各种数论问题和证明</li></ol><h3 id="💻-完整工具类示例">💻 完整工具类示例</h3><p>下面是一个完整的数论工具类，包含了上述所有算法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 数论工具类，提供常用的数论算法实现</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NumberTheoryUtils</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 所有方法实现...</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 测试主函数</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 测试最大公约数</span></span><br><span class="line">        System.out.println(<span class="string">&quot;GCD(48, 18) = &quot;</span> + gcd(<span class="number">48</span>, <span class="number">18</span>)); <span class="comment">// 应该输出6</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 测试最小公倍数</span></span><br><span class="line">        System.out.println(<span class="string">&quot;LCM(4, 6) = &quot;</span> + lcm(<span class="number">4</span>, <span class="number">6</span>)); <span class="comment">// 应该输出12</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 测试素数判断</span></span><br><span class="line">        System.out.println(<span class="string">&quot;Is 17 prime? &quot;</span> + isPrime(<span class="number">17</span>)); <span class="comment">// 应该输出true</span></span><br><span class="line">        System.out.println(<span class="string">&quot;Is 15 prime? &quot;</span> + isPrime(<span class="number">15</span>)); <span class="comment">// 应该输出false</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 测试素因数分解</span></span><br><span class="line">        System.out.println(<span class="string">&quot;Prime factors of 100: &quot;</span> + primeFactors(<span class="number">100</span>)); <span class="comment">// 应该输出[2, 2, 5, 5]</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 测试埃拉托斯特尼筛法</span></span><br><span class="line">        System.out.println(<span class="string">&quot;Primes &lt;= 30: &quot;</span> + sieveOfEratosthenes(<span class="number">30</span>)); <span class="comment">// 应该输出所有小于等于30的素数</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 测试欧拉函数</span></span><br><span class="line">        System.out.println(<span class="string">&quot;Euler&#x27;s totient of 10: &quot;</span> + eulerTotient(<span class="number">10</span>)); <span class="comment">// 应该输出4</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 测试互质判断</span></span><br><span class="line">        System.out.println(<span class="string">&quot;Are 8 and 15 coprime? &quot;</span> + areCoprime(<span class="number">8</span>, <span class="number">15</span>)); <span class="comment">// 应该输出true</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 测试扩展欧几里得算法</span></span><br><span class="line">        <span class="type">int</span>[] extendedResult = extendedGcd(<span class="number">48</span>, <span class="number">18</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;Extended GCD(48, 18): &quot;</span> + </span><br><span class="line">                          <span class="string">&quot;gcd=&quot;</span> + extendedResult[<span class="number">0</span>] + </span><br><span class="line">                          <span class="string">&quot;, x=&quot;</span> + extendedResult[<span class="number">1</span>] + </span><br><span class="line">                          <span class="string">&quot;, y=&quot;</span> + extendedResult[<span class="number">2</span>]);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="📚-进阶学习资源">📚 进阶学习资源</h3><ul><li><strong>算法导论</strong>：第31章详细介绍了数论算法</li><li><strong>密码学原理与实践</strong>：介绍了数论在密码学中的应用</li><li><strong>Competitive Programming 3</strong>：包含更多高级数论算法</li></ul><hr>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;数论算法：最大公约数与素数相关算法详解&lt;/h1&gt;
&lt;h2 id=&quot;🎯-学习目标&quot;&gt;🎯 学习目标&lt;/h2&gt;
&lt;p&gt;通过本教程，你将掌握以下数论核心算法的Java实现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 最大公约数（GCD）的计算方法&lt;/li&gt;
&lt;li&gt;✅ 最小公倍数（LCM</summary>
      
    
    
    
    <category term="书山指路" scheme="https://blog.adoreorg.cn/categories/%E4%B9%A6%E5%B1%B1%E6%8C%87%E8%B7%AF/"/>
    
    
    <category term="算法" scheme="https://blog.adoreorg.cn/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="数论" scheme="https://blog.adoreorg.cn/tags/%E6%95%B0%E8%AE%BA/"/>
    
    <category term="数学" scheme="https://blog.adoreorg.cn/tags/%E6%95%B0%E5%AD%A6/"/>
    
  </entry>
  
  <entry>
    <title>泰勒·斯威夫特音乐宇宙：从乡村少女到流行女王的进化史</title>
    <link href="https://blog.adoreorg.cn/posts/23j4osde.html"/>
    <id>https://blog.adoreorg.cn/posts/23j4osde.html</id>
    <published>2025-08-19T12:00:00.000Z</published>
    <updated>2025-09-20T05:22:02.770Z</updated>
    
    <content type="html"><![CDATA[<style>.taylor-album {  display: flex;  flex-wrap: wrap;  gap: 20px;  margin: 20px 0;}.album-card {  flex: 1 1 300px;  border-radius: 15px;  overflow: hidden;  box-shadow: 0 4px 15px rgba(0,0,0,0.1);  transition: transform 0.3s ease;}.album-card:hover {  transform: translateY(-5px);}.album-cover {  width: 100%;  height: 200px;  object-fit: cover;}.album-info {  padding: 15px;  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);  color: white;}.album-year {  font-size: 0.9em;  opacity: 0.8;}.album-title {  font-size: 1.2em;  font-weight: bold;  margin: 5px 0;}.song-list {  list-style: none;  padding: 0;}.song-list li {  padding: 5px 0;  border-bottom: 1px solid rgba(255,255,255,0.2);}.taylor-timeline {  position: relative;  padding: 20px 0;}.timeline-item {  display: flex;  margin-bottom: 30px;  align-items: center;}.timeline-year {  font-size: 1.5em;  font-weight: bold;  color: #e91e63;  min-width: 80px;}.timeline-content {  flex: 1;  margin-left: 20px;  padding: 15px;  background: rgba(255,255,255,0.1);  border-radius: 10px;}</style><blockquote><p>“People haven’t always been there for me but music always has.” - Taylor Swift</p></blockquote><h2 id="🎵-泰勒的音乐进化之路">🎵 泰勒的音乐进化之路</h2><p>从16岁的乡村音乐小公主到全球瞩目的流行音乐女王，Taylor Swift用15年时间完成了音乐史上最华丽的转身。今天让我们一起走进她的音乐宇宙，探索那些定义了一个时代的经典作品。</p><h2 id="📀-时代专辑巡礼">📀 时代专辑巡礼</h2><div class="taylor-album">  <div class="album-card">    <img class="album-cover" src="https://images.unsplash.com/photo-1614613535308-eb5fbd3d2c17?w=400&h=400&fit=crop" alt="Taylor Swift Debut">    <div class="album-info">      <div class="album-year">2006</div>      <div class="album-title">Taylor Swift</div>      <ul class="song-list">        <li>Tim McGraw</li>        <li>Teardrops on My Guitar</li>        <li>Our Song</li>      </ul>    </div>  </div>  <div class="album-card">    <img class="album-cover" src="https://images.unsplash.com/photo-1619983081563-430f63602796?w=400&h=400&fit=crop" alt="Fearless">    <div class="album-info">      <div class="album-year">2008</div>      <div class="album-title">Fearless</div>      <ul class="song-list">        <li>Love Story</li>        <li>You Belong with Me</li>        <li>Fifteen</li>      </ul>    </div>  </div>  <div class="album-card">    <img class="album-cover" src="https://images.unsplash.com/photo-1614613535308-eb5fbd3d2c17?w=400&h=400&fit=crop" alt="1989">    <div class="album-info">      <div class="album-year">2014</div>      <div class="album-title">1989</div>      <ul class="song-list">        <li>Shake It Off</li>        <li>Blank Space</li>        <li>Style</li>      </ul>    </div>  </div>  <div class="album-card">    <img class="album-cover" src="https://images.unsplash.com/photo-1619983081563-430f63602796?w=400&h=400&fit=crop" alt="Midnights">    <div class="album-info">      <div class="album-year">2022</div>      <div class="album-title">Midnights</div>      <ul class="song-list">        <li>Anti-Hero</li>        <li>Lavender Haze</li>        <li>Karma</li>      </ul>    </div>  </div></div><h2 id="🕰️-音乐时代时间线">🕰️ 音乐时代时间线</h2><div class="taylor-timeline">  <div class="timeline-item">    <div class="timeline-year">2006-2010</div>    <div class="timeline-content">      <h3>乡村音乐时代</h3>      <p>以吉他为主导的乡村音乐风格，歌词充满青春故事和乡村情怀。代表作《Love Story》、《You Belong with Me》奠定了她在乡村音乐界的地位。</p>    </div>  </div>  <div class="timeline-item">    <div class="timeline-year">2012-2014</div>    <div class="timeline-content">      <h3>流行转型期</h3>      <p>从《Red》开始尝试流行元素，到《1989》彻底转型为流行音乐，展现了惊人的音乐适应能力和创作才华。</p>    </div>  </div>  <div class="timeline-item">    <div class="timeline-year">2017-2019</div>    <div class="timeline-content">      <h3>暗黑复仇期</h3>      <p>《Reputation》和《Lover》展现了更加成熟的音乐制作和更深层次的歌词表达，标志着她作为艺术家的完全成熟。</p>    </div>  </div>  <div class="timeline-item">    <div class="timeline-year">2020-现在</div>    <div class="timeline-content">      <h3>独立创作期</h3>      <p>疫情期间独立创作的《Folklore》和《Evermore》展现了她作为词曲作者的深厚功底，获得了格莱美年度专辑奖。</p>    </div>  </div></div><h2 id="🎧-必听经典推荐">🎧 必听经典推荐</h2><h3 id="入门必听-适合新粉丝">入门必听 (适合新粉丝)</h3><ol><li><strong>Love Story</strong> - 经典爱情故事，乡村音乐代表作</li><li><strong>Shake It Off</strong> - 转型流行音乐的代表作</li><li><strong>Blank Space</strong> - 讽刺媒体形象的神曲</li><li><strong>Anti-Hero</strong> - 最新代表作，展现内心挣扎</li></ol><h3 id="深度推荐-适合资深粉丝">深度推荐 (适合资深粉丝)</h3><ol><li><strong>All Too Well (10 Minute Version)</strong> - 被誉为最佳分手歌曲</li><li><strong>Cruel Summer</strong> - 夏日恋曲的完美诠释</li><li><strong>The Archer</strong> - 内心独白的极致表达</li><li><strong>Maroon</strong> - 成熟期的代表作</li></ol><h3 id="隐藏宝藏">隐藏宝藏</h3><ol><li><strong>Death by a Thousand Cuts</strong> - 电影《Someone Great》灵感</li><li><strong>Right Where You Left Me</strong> - 被遗弃的角落感</li><li><strong>Would’ve, Could’ve, Should’ve</strong> - 青春遗憾的回响</li></ol><h2 id="📊-泰勒的音乐数据">📊 泰勒的音乐数据</h2><ul><li><strong>总专辑数</strong>: 10张录音室专辑</li><li><strong>格莱美奖</strong>: 12座</li><li><strong>全球销量</strong>: 超过2亿张</li><li><strong>Spotify月听众</strong>: 超过1亿</li><li><strong>最长Billboard榜单歌曲</strong>: “All Too Well (10 Minute Version)”</li></ul><h2 id="🎭-彩蛋：泰勒的隐藏信息">🎭 彩蛋：泰勒的隐藏信息</h2><p>泰勒以在作品中隐藏彩蛋而闻名：</p><ul><li><strong>数字彩蛋</strong>: 专辑发布日期往往有特殊含义</li><li><strong>歌词彩蛋</strong>: 新专辑常包含对旧作品的呼应</li><li><strong>视觉彩蛋</strong>: MV中充满象征意义的画面</li><li><strong>社交媒体彩蛋</strong>: 精心策划的发布策略</li></ul><h2 id="🎪-现场体验推荐">🎪 现场体验推荐</h2><p>如果你有机会观看泰勒的现场演出：</p><h3 id="The-Eras-Tour-时代巡演">The Eras Tour (时代巡演)</h3><ul><li><strong>时长</strong>: 3.5小时</li><li><strong>歌曲数量</strong>: 40+首</li><li><strong>特色</strong>: 每个时代都有独特造型和舞台设计</li><li><strong>亮点</strong>: 10分钟版本的All Too Well现场</li></ul><h2 id="💡-粉丝小贴士">💡 粉丝小贴士</h2><ol><li><strong>如何开始</strong>: 从《1989》开始，然后向前探索乡村时代</li><li><strong>歌词解读</strong>: 关注时间线和情感变化</li><li><strong>MV观看</strong>: 注意细节和象征意义</li><li><strong>演唱会</strong>: 提前熟悉所有时代的歌曲</li></ol><hr><blockquote><p>“I think fearless is having fears but jumping anyway.” - Taylor Swift</p></blockquote><h2 id="🎵-立即聆听">🎵 立即聆听</h2><div id="aplayer"></div><script>document.addEventListener('DOMContentLoaded', function() {  const ap = new APlayer({    container: document.getElementById('aplayer'),    audio: [      {        name: 'Blank Space',        artist: 'Taylor Swift',        url: 'https://music.163.com/song/media/outer/url?id=1440867126.mp3',        cover: 'https://p1.music.126.net/diGAyEmpymX8G7JcnElncQ==/5968269502834788.jpg'      },      {        name: 'Shake It Off',        artist: 'Taylor Swift',        url: 'https://music.163.com/song/media/outer/url?id=1440867125.mp3',        cover: 'https://p1.music.126.net/diGAyEmpymX8G7JcnElncQ==/5968269502834788.jpg'      },      {        name: 'I Knew You Were Trouble.',        artist: 'Taylor Swift',        url: 'https://music.163.com/song/media/outer/url?id=1440867132.mp3',        cover: 'https://p1.music.126.net/2d7OGK6CYbSbrJ9uOH7vGQ==/10995116569056893.jpg'      },      {        name: 'Love Story',        artist: 'Taylor Swift',        url: 'https://music.163.com/song/media/outer/url?id=1440867124.mp3',        cover: 'https://p1.music.126.net/2d7OGK6CYbSbrJ9uOH7vGQ==/10995116569056893.jpg'      },      {        name: 'You Belong With Me',        artist: 'Taylor Swift',        url: 'https://music.163.com/song/media/outer/url?id=1440867129.mp3',        cover: 'https://p1.music.126.net/2d7OGK6CYbSbrJ9uOH7vGQ==/10995116569056893.jpg'      },      {        name: 'We Are Never Ever Getting Back Together',        artist: 'Taylor Swift',        url: 'https://music.163.com/song/media/outer/url?id=1440867134.mp3',        cover: 'https://p1.music.126.net/2d7OGK6CYbSbrJ9uOH7vGQ==/10995116569056893.jpg'      }    ]  });});</script><hr><p><em>最后更新时间：2024年12月19日</em><br><em>作者：adore🍊</em><br><em>欢迎在评论区分享你最爱的泰勒歌曲！</em></p>]]></content>
    
    
    <summary type="html">泰勒·斯威夫特（Taylor Swift）是美国的流行音乐歌手、词曲作者和演员。她于2006年12月14日出生于美国纽约市，是一个来自乡村的少女，她的音乐风格独特而丰富。</summary>
    
    
    
    <category term="游心雅叙" scheme="https://blog.adoreorg.cn/categories/%E6%B8%B8%E5%BF%83%E9%9B%85%E5%8F%99/"/>
    
    
    <category term="Taylor Swift" scheme="https://blog.adoreorg.cn/tags/Taylor-Swift/"/>
    
    <category term="音乐" scheme="https://blog.adoreorg.cn/tags/%E9%9F%B3%E4%B9%90/"/>
    
    <category term="流行音乐" scheme="https://blog.adoreorg.cn/tags/%E6%B5%81%E8%A1%8C%E9%9F%B3%E4%B9%90/"/>
    
    <category term="乡村音乐" scheme="https://blog.adoreorg.cn/tags/%E4%B9%A1%E6%9D%91%E9%9F%B3%E4%B9%90/"/>
    
    <category term="音乐推荐" scheme="https://blog.adoreorg.cn/tags/%E9%9F%B3%E4%B9%90%E6%8E%A8%E8%8D%90/"/>
    
  </entry>
  
  <entry>
    <title>MySQL全面指南：从基础到性能优化</title>
    <link href="https://blog.adoreorg.cn/posts/b9255ce6.html"/>
    <id>https://blog.adoreorg.cn/posts/b9255ce6.html</id>
    <published>2025-08-19T07:00:00.000Z</published>
    <updated>2025-08-19T07:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1>MySQL全面指南：从基础到性能优化</h1><p>MySQL是世界上最流行的开源关系型数据库管理系统之一，广泛应用于Web应用、企业级系统和云原生架构。本文将带你深入了解MySQL的方方面面。</p><h2 id="什么是MySQL？">什么是MySQL？</h2><p>MySQL是一个开源的关系型数据库管理系统(RDBMS)，使用结构化查询语言(SQL)进行数据库管理。它以其可靠性、高性能和易用性而闻名，是LAMP(Linux, Apache, MySQL, PHP/Python/Perl)技术栈的重要组成部分。</p><h3 id="MySQL的核心特点">MySQL的核心特点</h3><ul><li><strong>开源免费</strong>：社区版完全免费，源代码开放</li><li><strong>跨平台</strong>：支持Windows、Linux、macOS等主流操作系统</li><li><strong>高性能</strong>：优化的SQL查询引擎，支持大量并发连接</li><li><strong>可靠性</strong>：ACID事务支持，数据完整性保障</li><li><strong>易用性</strong>：丰富的管理工具和完善的文档支持</li><li><strong>可扩展</strong>：支持主从复制、集群部署等高可用架构</li></ul><h2 id="安装MySQL">安装MySQL</h2><h3 id="macOS安装">macOS安装</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用Homebrew安装</span></span><br><span class="line">brew install mysql</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动MySQL服务</span></span><br><span class="line">brew services start mysql</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置root密码</span></span><br><span class="line">mysql_secure_installation</span><br><span class="line"></span><br><span class="line"><span class="comment"># 连接到MySQL</span></span><br><span class="line">mysql -u root -p</span><br></pre></td></tr></table></figure><h3 id="Linux安装">Linux安装</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Ubuntu/Debian</span></span><br><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt install mysql-server mysql-client</span><br><span class="line"></span><br><span class="line"><span class="comment"># CentOS/RHEL</span></span><br><span class="line"><span class="built_in">sudo</span> yum install mysql-server mysql</span><br><span class="line"><span class="built_in">sudo</span> systemctl start mysqld</span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> mysqld</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取临时密码</span></span><br><span class="line"><span class="built_in">sudo</span> grep <span class="string">&#x27;temporary password&#x27;</span> /var/log/mysqld.log</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安全配置</span></span><br><span class="line"><span class="built_in">sudo</span> mysql_secure_installation</span><br></pre></td></tr></table></figure><h3 id="Docker安装">Docker安装</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 拉取MySQL镜像</span></span><br><span class="line">docker pull mysql:8.0</span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行MySQL容器</span></span><br><span class="line">docker run -d \</span><br><span class="line">  --name mysql \</span><br><span class="line">  -p 3306:3306 \</span><br><span class="line">  -e MYSQL_ROOT_PASSWORD=yourpassword \</span><br><span class="line">  -v mysql_data:/var/lib/mysql \</span><br><span class="line">  mysql:8.0</span><br><span class="line"></span><br><span class="line"><span class="comment"># 连接到容器中的MySQL</span></span><br><span class="line">docker <span class="built_in">exec</span> -it mysql mysql -u root -p</span><br></pre></td></tr></table></figure><h2 id="MySQL基础操作">MySQL基础操作</h2><h3 id="数据库操作">数据库操作</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 创建数据库</span></span><br><span class="line"><span class="keyword">CREATE</span> DATABASE myapp <span class="keyword">CHARACTER SET</span> utf8mb4 <span class="keyword">COLLATE</span> utf8mb4_unicode_ci;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看数据库</span></span><br><span class="line"><span class="keyword">SHOW</span> DATABASES;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 选择数据库</span></span><br><span class="line">USE myapp;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 删除数据库</span></span><br><span class="line"><span class="keyword">DROP</span> DATABASE myapp;</span><br></pre></td></tr></table></figure><h3 id="数据表操作">数据表操作</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 创建用户表</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> users (</span><br><span class="line">    id <span class="type">INT</span> AUTO_INCREMENT <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">    username <span class="type">VARCHAR</span>(<span class="number">50</span>) <span class="keyword">NOT NULL</span> <span class="keyword">UNIQUE</span>,</span><br><span class="line">    email <span class="type">VARCHAR</span>(<span class="number">100</span>) <span class="keyword">NOT NULL</span> <span class="keyword">UNIQUE</span>,</span><br><span class="line">    password <span class="type">VARCHAR</span>(<span class="number">255</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">    created_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span>,</span><br><span class="line">    updated_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> <span class="keyword">ON</span> <span class="keyword">UPDATE</span> <span class="built_in">CURRENT_TIMESTAMP</span>,</span><br><span class="line">    INDEX idx_username (username),</span><br><span class="line">    INDEX idx_email (email)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 创建文章表</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> posts (</span><br><span class="line">    id <span class="type">INT</span> AUTO_INCREMENT <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">    user_id <span class="type">INT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">    title <span class="type">VARCHAR</span>(<span class="number">200</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">    content TEXT,</span><br><span class="line">    status ENUM(<span class="string">&#x27;draft&#x27;</span>, <span class="string">&#x27;published&#x27;</span>, <span class="string">&#x27;archived&#x27;</span>) <span class="keyword">DEFAULT</span> <span class="string">&#x27;draft&#x27;</span>,</span><br><span class="line">    created_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span>,</span><br><span class="line">    updated_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> <span class="keyword">ON</span> <span class="keyword">UPDATE</span> <span class="built_in">CURRENT_TIMESTAMP</span>,</span><br><span class="line">    <span class="keyword">FOREIGN KEY</span> (user_id) <span class="keyword">REFERENCES</span> users(id) <span class="keyword">ON</span> <span class="keyword">DELETE</span> CASCADE,</span><br><span class="line">    INDEX idx_user_id (user_id),</span><br><span class="line">    INDEX idx_status (status),</span><br><span class="line">    FULLTEXT idx_title_content (title, content)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看表结构</span></span><br><span class="line"><span class="keyword">DESCRIBE</span> users;</span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">CREATE TABLE</span> users;</span><br></pre></td></tr></table></figure><h3 id="数据操作语言-DML">数据操作语言(DML)</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 插入数据</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> users (username, email, password) <span class="keyword">VALUES</span> </span><br><span class="line">(<span class="string">&#x27;alice&#x27;</span>, <span class="string">&#x27;alice@example.com&#x27;</span>, <span class="string">&#x27;hashed_password&#x27;</span>),</span><br><span class="line">(<span class="string">&#x27;bob&#x27;</span>, <span class="string">&#x27;bob@example.com&#x27;</span>, <span class="string">&#x27;hashed_password&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 批量插入</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> posts (user_id, title, content, status) <span class="keyword">VALUES</span></span><br><span class="line">(<span class="number">1</span>, <span class="string">&#x27;MySQL入门指南&#x27;</span>, <span class="string">&#x27;这是一篇关于MySQL的文章...&#x27;</span>, <span class="string">&#x27;published&#x27;</span>),</span><br><span class="line">(<span class="number">1</span>, <span class="string">&#x27;Redis使用技巧&#x27;</span>, <span class="string">&#x27;Redis是一个强大的缓存数据库...&#x27;</span>, <span class="string">&#x27;draft&#x27;</span>),</span><br><span class="line">(<span class="number">2</span>, <span class="string">&#x27;Web开发最佳实践&#x27;</span>, <span class="string">&#x27;分享一些Web开发的经验...&#x27;</span>, <span class="string">&#x27;published&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询数据</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> users <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">SELECT</span> username, email <span class="keyword">FROM</span> users LIMIT <span class="number">10</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> posts <span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;published&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 更新数据</span></span><br><span class="line"><span class="keyword">UPDATE</span> users <span class="keyword">SET</span> email <span class="operator">=</span> <span class="string">&#x27;newemail@example.com&#x27;</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> posts <span class="keyword">SET</span> status <span class="operator">=</span> <span class="string">&#x27;published&#x27;</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 删除数据</span></span><br><span class="line"><span class="keyword">DELETE</span> <span class="keyword">FROM</span> posts <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line"><span class="keyword">DELETE</span> <span class="keyword">FROM</span> users <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">2</span>;</span><br></pre></td></tr></table></figure><h2 id="高级查询技巧">高级查询技巧</h2><h3 id="复杂查询">复杂查询</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 连接查询</span></span><br><span class="line"><span class="keyword">SELECT</span> u.username, p.title, p.status, p.created_at</span><br><span class="line"><span class="keyword">FROM</span> users u</span><br><span class="line"><span class="keyword">JOIN</span> posts p <span class="keyword">ON</span> u.id <span class="operator">=</span> p.user_id</span><br><span class="line"><span class="keyword">WHERE</span> p.status <span class="operator">=</span> <span class="string">&#x27;published&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> p.created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 子查询</span></span><br><span class="line"><span class="keyword">SELECT</span> username, email</span><br><span class="line"><span class="keyword">FROM</span> users</span><br><span class="line"><span class="keyword">WHERE</span> id <span class="keyword">IN</span> (<span class="keyword">SELECT</span> <span class="keyword">DISTINCT</span> user_id <span class="keyword">FROM</span> posts <span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="string">&#x27;published&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 分组统计</span></span><br><span class="line"><span class="keyword">SELECT</span> u.username, <span class="built_in">COUNT</span>(p.id) <span class="keyword">as</span> post_count</span><br><span class="line"><span class="keyword">FROM</span> users u</span><br><span class="line"><span class="keyword">LEFT</span> <span class="keyword">JOIN</span> posts p <span class="keyword">ON</span> u.id <span class="operator">=</span> p.user_id</span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> u.id, u.username</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> post_count <span class="keyword">DESC</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 窗口函数（MySQL 8.0+）</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    id, </span><br><span class="line">    title, </span><br><span class="line">    user_id,</span><br><span class="line">    created_at,</span><br><span class="line">    <span class="built_in">ROW_NUMBER</span>() <span class="keyword">OVER</span> (<span class="keyword">PARTITION</span> <span class="keyword">BY</span> user_id <span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span>) <span class="keyword">as</span> user_post_rank</span><br><span class="line"><span class="keyword">FROM</span> posts</span><br><span class="line"><span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="string">&#x27;published&#x27;</span>;</span><br></pre></td></tr></table></figure><h3 id="索引优化">索引优化</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 创建索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_posts_created_at <span class="keyword">ON</span> posts(created_at);</span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_posts_user_status <span class="keyword">ON</span> posts(user_id, status);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 复合索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_posts_composite <span class="keyword">ON</span> posts(user_id, status, created_at);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 唯一索引</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">UNIQUE</span> INDEX idx_users_username <span class="keyword">ON</span> users(username);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看索引使用情况</span></span><br><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> posts <span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;published&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 分析索引统计信息</span></span><br><span class="line"><span class="keyword">SHOW</span> INDEX <span class="keyword">FROM</span> posts;</span><br></pre></td></tr></table></figure><h2 id="事务处理">事务处理</h2><h3 id="ACID特性">ACID特性</h3><p>MySQL的InnoDB存储引擎支持ACID事务：</p><ul><li><strong>原子性(Atomicity)</strong>：事务中的所有操作要么全部完成，要么全部不完成</li><li><strong>一致性(Consistency)</strong>：事务开始和完成时，数据都必须保持一致状态</li><li><strong>隔离性(Isolation)</strong>：并发事务之间相互隔离，互不干扰</li><li><strong>持久性(Durability)</strong>：事务完成后，对数据的修改是永久性的</li></ul><h3 id="事务操作">事务操作</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 开始事务</span></span><br><span class="line"><span class="keyword">START</span> TRANSACTION;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 执行操作</span></span><br><span class="line"><span class="keyword">UPDATE</span> accounts <span class="keyword">SET</span> balance <span class="operator">=</span> balance <span class="operator">-</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> accounts <span class="keyword">SET</span> balance <span class="operator">=</span> balance <span class="operator">+</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 记录交易日志</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> transactions (from_account, to_account, amount) <span class="keyword">VALUES</span> (<span class="number">1</span>, <span class="number">2</span>, <span class="number">100</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 提交事务</span></span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 回滚事务</span></span><br><span class="line"><span class="keyword">ROLLBACK</span>;</span><br></pre></td></tr></table></figure><h3 id="事务隔离级别">事务隔离级别</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 查看当前隔离级别</span></span><br><span class="line"><span class="keyword">SELECT</span> @<span class="variable">@transaction_isolation</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 设置隔离级别</span></span><br><span class="line"><span class="keyword">SET</span> SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 四种隔离级别</span></span><br><span class="line"><span class="comment">-- READ UNCOMMITTED: 读未提交，可能出现脏读</span></span><br><span class="line"><span class="comment">-- READ COMMITTED: 读已提交，避免脏读</span></span><br><span class="line"><span class="comment">-- REPEATABLE READ: 可重复读，避免不可重复读（MySQL默认）</span></span><br><span class="line"><span class="comment">-- SERIALIZABLE: 串行化，避免幻读</span></span><br></pre></td></tr></table></figure><h2 id="性能优化技巧">性能优化技巧</h2><h3 id="查询优化">查询优化</h3><ol><li><strong>使用EXPLAIN分析查询</strong></li></ol><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">EXPLAIN FORMAT<span class="operator">=</span>JSON <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> posts p </span><br><span class="line"><span class="keyword">JOIN</span> users u <span class="keyword">ON</span> p.user_id <span class="operator">=</span> u.id </span><br><span class="line"><span class="keyword">WHERE</span> p.status <span class="operator">=</span> <span class="string">&#x27;published&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> u.username <span class="operator">=</span> <span class="string">&#x27;alice&#x27;</span>;</span><br></pre></td></tr></table></figure><ol start="2"><li><strong>避免全表扫描</strong></li></ol><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 不好的做法</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> users <span class="keyword">WHERE</span> <span class="type">DATE</span>(created_at) <span class="operator">=</span> <span class="string">&#x27;2024-01-01&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 好的做法</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> users </span><br><span class="line"><span class="keyword">WHERE</span> created_at <span class="operator">&gt;=</span> <span class="string">&#x27;2024-01-01&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> created_at <span class="operator">&lt;</span> <span class="string">&#x27;2024-01-02&#x27;</span>;</span><br></pre></td></tr></table></figure><ol start="3"><li><strong>合理使用索引</strong></li></ol><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 创建覆盖索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_covering <span class="keyword">ON</span> posts(user_id, status, created_at, title);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 使用索引提示</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> posts USE INDEX (idx_user_status) <span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1</span>;</span><br></pre></td></tr></table></figure><h3 id="表结构优化">表结构优化</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 选择合适的数据类型</span></span><br><span class="line"><span class="comment">-- 使用TINYINT代替INT当可能时</span></span><br><span class="line"><span class="comment">-- 使用VARCHAR而不是TEXT当可能时</span></span><br><span class="line"><span class="comment">-- 使用TIMESTAMP而不是DATETIME当可能时</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 分区表（大表优化）</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> logs (</span><br><span class="line">    id <span class="type">INT</span> AUTO_INCREMENT,</span><br><span class="line">    log_date <span class="type">DATE</span>,</span><br><span class="line">    message TEXT,</span><br><span class="line">    <span class="keyword">PRIMARY KEY</span> (id, log_date)</span><br><span class="line">) <span class="keyword">PARTITION</span> <span class="keyword">BY</span> <span class="keyword">RANGE</span> (<span class="keyword">YEAR</span>(log_date)) (</span><br><span class="line">    <span class="keyword">PARTITION</span> p2023 <span class="keyword">VALUES</span> LESS THAN (<span class="number">2024</span>),</span><br><span class="line">    <span class="keyword">PARTITION</span> p2024 <span class="keyword">VALUES</span> LESS THAN (<span class="number">2025</span>),</span><br><span class="line">    <span class="keyword">PARTITION</span> pmax <span class="keyword">VALUES</span> LESS THAN MAXVALUE</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="配置优化">配置优化</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 查看当前配置</span></span><br><span class="line"><span class="keyword">SHOW</span> VARIABLES <span class="keyword">LIKE</span> <span class="string">&#x27;innodb_buffer_pool_size&#x27;</span>;</span><br><span class="line"><span class="keyword">SHOW</span> VARIABLES <span class="keyword">LIKE</span> <span class="string">&#x27;query_cache_size&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 关键配置参数</span></span><br><span class="line"><span class="comment">-- innodb_buffer_pool_size: 建议设置为物理内存的60-80%</span></span><br><span class="line"><span class="comment">-- max_connections: 根据实际需求调整</span></span><br><span class="line"><span class="comment">-- query_cache_size: 查询缓存大小（MySQL 8.0已弃用）</span></span><br><span class="line"><span class="comment">-- tmp_table_size: 临时表大小限制</span></span><br></pre></td></tr></table></figure><h2 id="备份与恢复">备份与恢复</h2><h3 id="使用mysqldump备份">使用mysqldump备份</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 备份整个数据库</span></span><br><span class="line">mysqldump -u root -p myapp &gt; myapp_backup.sql</span><br><span class="line"></span><br><span class="line"><span class="comment"># 备份特定表</span></span><br><span class="line">mysqldump -u root -p myapp <span class="built_in">users</span> posts &gt; tables_backup.sql</span><br><span class="line"></span><br><span class="line"><span class="comment"># 压缩备份</span></span><br><span class="line">mysqldump -u root -p myapp | gzip &gt; myapp_backup.sql.gz</span><br><span class="line"></span><br><span class="line"><span class="comment"># 备份结构（不包含数据）</span></span><br><span class="line">mysqldump -u root -p --no-data myapp &gt; schema_backup.sql</span><br></pre></td></tr></table></figure><h3 id="恢复数据">恢复数据</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 恢复数据库</span></span><br><span class="line">mysql -u root -p myapp &lt; myapp_backup.sql</span><br><span class="line"></span><br><span class="line"><span class="comment"># 从压缩文件恢复</span></span><br><span class="line">gunzip &lt; myapp_backup.sql.gz | mysql -u root -p myapp</span><br></pre></td></tr></table></figure><h3 id="使用物理备份">使用物理备份</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用xtrabackup进行热备份</span></span><br><span class="line">xtrabackup --backup --target-dir=/backups/mysql/</span><br><span class="line"></span><br><span class="line"><span class="comment"># 准备备份</span></span><br><span class="line">xtrabackup --prepare --target-dir=/backups/mysql/</span><br><span class="line"></span><br><span class="line"><span class="comment"># 恢复备份</span></span><br><span class="line">xtrabackup --copy-back --target-dir=/backups/mysql/</span><br></pre></td></tr></table></figure><h2 id="高可用架构">高可用架构</h2><h3 id="主从复制">主从复制</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 在主服务器上创建复制用户</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">USER</span> <span class="string">&#x27;replication&#x27;</span>@<span class="string">&#x27;%&#x27;</span> IDENTIFIED <span class="keyword">BY</span> <span class="string">&#x27;password&#x27;</span>;</span><br><span class="line"><span class="keyword">GRANT</span> REPLICATION SLAVE <span class="keyword">ON</span> <span class="operator">*</span>.<span class="operator">*</span> <span class="keyword">TO</span> <span class="string">&#x27;replication&#x27;</span>@<span class="string">&#x27;%&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看主服务器状态</span></span><br><span class="line"><span class="keyword">SHOW</span> MASTER STATUS;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 在从服务器上配置复制</span></span><br><span class="line">CHANGE MASTER <span class="keyword">TO</span></span><br><span class="line">  MASTER_HOST<span class="operator">=</span><span class="string">&#x27;master_ip&#x27;</span>,</span><br><span class="line">  MASTER_USER<span class="operator">=</span><span class="string">&#x27;replication&#x27;</span>,</span><br><span class="line">  MASTER_PASSWORD<span class="operator">=</span><span class="string">&#x27;password&#x27;</span>,</span><br><span class="line">  MASTER_LOG_FILE<span class="operator">=</span><span class="string">&#x27;mysql-bin.000001&#x27;</span>,</span><br><span class="line">  MASTER_LOG_POS<span class="operator">=</span><span class="number">107</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 启动复制</span></span><br><span class="line"><span class="keyword">START</span> SLAVE;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看复制状态</span></span><br><span class="line"><span class="keyword">SHOW</span> SLAVE STATUS\G</span><br></pre></td></tr></table></figure><h3 id="读写分离">读写分离</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 使用MySQL Router实现读写分离</span></span><br><span class="line"><span class="comment">-- 或者使用中间件如ProxySQL、MyCAT</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 应用层读写分离示例</span></span><br><span class="line"><span class="comment">-- 读操作：连接到从库</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> users <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 写操作：连接到主库</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> users (username, email) <span class="keyword">VALUES</span> (<span class="string">&#x27;newuser&#x27;</span>, <span class="string">&#x27;new@example.com&#x27;</span>);</span><br></pre></td></tr></table></figure><h2 id="监控与诊断">监控与诊断</h2><h3 id="性能监控">性能监控</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 查看当前连接</span></span><br><span class="line"><span class="keyword">SHOW</span> PROCESSLIST;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看InnoDB状态</span></span><br><span class="line"><span class="keyword">SHOW</span> ENGINE INNODB STATUS\G</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看表统计信息</span></span><br><span class="line">ANALYZE <span class="keyword">TABLE</span> users;</span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">TABLE</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;users&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 慢查询日志</span></span><br><span class="line"><span class="comment">-- 启用慢查询日志</span></span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> slow_query_log <span class="operator">=</span> <span class="string">&#x27;ON&#x27;</span>;</span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> long_query_time <span class="operator">=</span> <span class="number">2</span>;</span><br></pre></td></tr></table></figure><h3 id="常用监控工具">常用监控工具</h3><ol><li><strong>MySQL Enterprise Monitor</strong> - 官方监控工具</li><li><strong>Percona Toolkit</strong> - 开源的MySQL管理工具集</li><li><strong>MySQL Workbench</strong> - 图形化管理工具</li><li><strong>pt-query-digest</strong> - 查询分析工具</li></ol><h2 id="最佳实践总结">最佳实践总结</h2><h3 id="设计规范">设计规范</h3><ol><li><p><strong>命名规范</strong></p><ul><li>使用小写字母和下划线</li><li>表名使用复数形式（users, posts）</li><li>字段名避免使用保留字</li></ul></li><li><p><strong>数据类型选择</strong></p><ul><li>选择最小的合适数据类型</li><li>避免使用NULL，设置合理的默认值</li><li>使用UNSIGNED存储非负整数</li></ul></li><li><p><strong>索引策略</strong></p><ul><li>为WHERE、JOIN、ORDER BY子句中的列创建索引</li><li>避免过多的索引（影响写入性能）</li><li>定期分析和优化索引</li></ul></li></ol><h3 id="安全实践">安全实践</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 创建专用用户</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">USER</span> <span class="string">&#x27;app_user&#x27;</span>@<span class="string">&#x27;localhost&#x27;</span> IDENTIFIED <span class="keyword">BY</span> <span class="string">&#x27;strong_password&#x27;</span>;</span><br><span class="line"><span class="keyword">GRANT</span> <span class="keyword">SELECT</span>, <span class="keyword">INSERT</span>, <span class="keyword">UPDATE</span>, <span class="keyword">DELETE</span> <span class="keyword">ON</span> myapp.<span class="operator">*</span> <span class="keyword">TO</span> <span class="string">&#x27;app_user&#x27;</span>@<span class="string">&#x27;localhost&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 定期更新密码</span></span><br><span class="line"><span class="keyword">ALTER</span> <span class="keyword">USER</span> <span class="string">&#x27;app_user&#x27;</span>@<span class="string">&#x27;localhost&#x27;</span> IDENTIFIED <span class="keyword">BY</span> <span class="string">&#x27;new_strong_password&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 限制连接数</span></span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> max_user_connections <span class="operator">=</span> <span class="number">100</span>;</span><br></pre></td></tr></table></figure><h2 id="结语">结语</h2><p>MySQL作为一个成熟的关系型数据库，提供了丰富的功能和强大的性能。通过本文的学习，你应该已经掌握了MySQL的核心概念、SQL语法、性能优化和高可用架构设计。</p><p>记住，优秀的数据库设计需要：</p><ul><li>合理的表结构设计</li><li>有效的索引策略</li><li>谨慎的事务使用</li><li>持续的性能监控</li></ul><p>继续深入学习MySQL的集群部署、分库分表、分布式事务等高级特性，将帮助你构建更加健壮和高效的数据库系统。</p><hr><h2 id="参考资料">参考资料</h2><ul><li><a href="https://dev.mysql.com/doc/">MySQL官方文档</a></li><li><a href="https://dev.mysql.com/doc/refman/8.0/en/">MySQL参考手册</a></li><li><a href="https://www.oreilly.com/library/view/high-performance-mysql/9781492080503/">高性能MySQL</a></li><li><a href="http://mysql.taobao.org/monthly/">MySQL技术内幕</a></li></ul>]]></content>
    
    
    <summary type="html">全面介绍MySQL数据库的核心概念、安装配置、SQL语法、索引优化、事务处理以及性能调优技巧，帮助开发者构建高效稳定的数据库应用。</summary>
    
    
    
    <category term="笔耕问道" scheme="https://blog.adoreorg.cn/categories/%E7%AC%94%E8%80%95%E9%97%AE%E9%81%93/"/>
    
    
    <category term="mysql" scheme="https://blog.adoreorg.cn/tags/mysql/"/>
    
    <category term="数据库" scheme="https://blog.adoreorg.cn/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    <category term="SQL" scheme="https://blog.adoreorg.cn/tags/SQL/"/>
    
    <category term="性能优化" scheme="https://blog.adoreorg.cn/tags/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    
    <category term="索引" scheme="https://blog.adoreorg.cn/tags/%E7%B4%A2%E5%BC%95/"/>
    
  </entry>
  
  <entry>
    <title>Redis入门指南：从安装到实战应用</title>
    <link href="https://blog.adoreorg.cn/posts/58a94673.html"/>
    <id>https://blog.adoreorg.cn/posts/58a94673.html</id>
    <published>2025-08-19T06:30:00.000Z</published>
    <updated>2025-08-19T06:30:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1>Redis入门指南：从安装到实战应用</h1><p>供了多种数据结构，如字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等。</p><h3 id="Redis的核心特点">Redis的核心特点</h3><ul><li><strong>内存存储</strong>：数据主要存储在内存中，读写速度极快</li><li><strong>数据结构丰富</strong>：支持多种数据结构，不仅限于简单的key-value</li><li><strong>持久化</strong>：支持RDB和AOF两种持久化方式</li><li><strong>高可用</strong>：支持主从复制、哨兵模式和集群模式</li><li><strong>原子操作</strong>：所有操作都是原子性的</li><li><strong>发布订阅</strong>：支持消息通信模式</li></ul><h2 id="安装Redis">安装Redis</h2><h3 id="macOS安装">macOS安装</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用Homebrew安装</span></span><br><span class="line">brew install redis</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动Redis服务</span></span><br><span class="line">brew services start redis</span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查Redis是否运行</span></span><br><span class="line">redis-cli ping</span><br><span class="line"><span class="comment"># 返回PONG表示运行正常</span></span><br></pre></td></tr></table></figure><h3 id="Linux安装">Linux安装</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Ubuntu/Debian</span></span><br><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt install redis-server</span><br><span class="line"></span><br><span class="line"><span class="comment"># CentOS/RHEL</span></span><br><span class="line"><span class="built_in">sudo</span> yum install redis</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动Redis</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl start redis</span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> redis</span><br></pre></td></tr></table></figure><h3 id="Docker安装">Docker安装</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 拉取Redis镜像</span></span><br><span class="line">docker pull redis:latest</span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行Redis容器</span></span><br><span class="line">docker run -d --name redis -p 6379:6379 redis</span><br><span class="line"></span><br><span class="line"><span class="comment"># 连接到Redis</span></span><br><span class="line">docker <span class="built_in">exec</span> -it redis redis-cli</span><br></pre></td></tr></table></figure><h2 id="Redis数据类型详解">Redis数据类型详解</h2><h3 id="1-字符串-String">1. 字符串(String)</h3><p>最基础的数据类型，可以存储字符串、整数或浮点数。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 设置值</span></span><br><span class="line">SET name <span class="string">&quot;Redis&quot;</span></span><br><span class="line">SET counter 100</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取值</span></span><br><span class="line">GET name</span><br><span class="line">GET counter</span><br><span class="line"></span><br><span class="line"><span class="comment"># 数值操作</span></span><br><span class="line">INCR counter      <span class="comment"># 自增1</span></span><br><span class="line">DECR counter      <span class="comment"># 自减1</span></span><br><span class="line">INCRBY counter 10 <span class="comment"># 增加指定值</span></span><br></pre></td></tr></table></figure><h3 id="2-哈希-Hash">2. 哈希(Hash)</h3><p>适合存储对象，每个hash可以存储多个键值对。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 存储用户信息</span></span><br><span class="line">HSET user:1000 name <span class="string">&quot;张三&quot;</span> age 25 city <span class="string">&quot;北京&quot;</span></span><br><span class="line">HSET user:1000 email <span class="string">&quot;zhangsan@example.com&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取用户信息</span></span><br><span class="line">HGETALL user:1000</span><br><span class="line">HGET user:1000 name</span><br><span class="line">HGET user:1000 age</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取所有字段</span></span><br><span class="line">HKEYS user:1000</span><br><span class="line">HVALS user:1000</span><br></pre></td></tr></table></figure><h3 id="3-列表-List">3. 列表(List)</h3><p>基于链表实现的列表，支持从两端插入和删除。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 从左侧插入</span></span><br><span class="line">LPUSH mylist <span class="string">&quot;a&quot;</span> <span class="string">&quot;b&quot;</span> <span class="string">&quot;c&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 从右侧插入</span></span><br><span class="line">RPUSH mylist <span class="string">&quot;d&quot;</span> <span class="string">&quot;e&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看列表</span></span><br><span class="line">LRANGE mylist 0 -1  <span class="comment"># 查看所有元素</span></span><br><span class="line">LRANGE mylist 0 2   <span class="comment"># 查看前3个元素</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 弹出元素</span></span><br><span class="line">LPOP mylist    <span class="comment"># 从左侧弹出</span></span><br><span class="line">RPOP mylist    <span class="comment"># 从右侧弹出</span></span><br></pre></td></tr></table></figure><h3 id="4-集合-Set">4. 集合(Set)</h3><p>无序的唯一元素集合。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 添加元素</span></span><br><span class="line">SADD tags <span class="string">&quot;redis&quot;</span> <span class="string">&quot;database&quot;</span> <span class="string">&quot;cache&quot;</span></span><br><span class="line">SADD tags <span class="string">&quot;redis&quot;</span>  <span class="comment"># 重复元素不会被添加</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看集合</span></span><br><span class="line">SMEMBERS tags</span><br><span class="line"></span><br><span class="line"><span class="comment"># 判断元素是否存在</span></span><br><span class="line">SISMEMBER tags <span class="string">&quot;redis&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 集合操作</span></span><br><span class="line">SUNION set1 set2      <span class="comment"># 并集</span></span><br><span class="line">SINTER set1 set2      <span class="comment"># 交集</span></span><br><span class="line">SDIFF set1 set2       <span class="comment"># 差集</span></span><br></pre></td></tr></table></figure><h3 id="5-有序集合-Sorted-Set">5. 有序集合(Sorted Set)</h3><p>每个成员都关联一个分数(score)，按分数排序。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 添加成员</span></span><br><span class="line">ZADD leaderboard 100 <span class="string">&quot;Alice&quot;</span> 200 <span class="string">&quot;Bob&quot;</span> 150 <span class="string">&quot;Charlie&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看排行榜</span></span><br><span class="line">ZREVRANGE leaderboard 0 -1 WITHSCORES</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取排名</span></span><br><span class="line">ZREVRANK leaderboard <span class="string">&quot;Bob&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取分数范围内的成员</span></span><br><span class="line">ZRANGEBYSCORE leaderboard 100 200</span><br></pre></td></tr></table></figure><h2 id="Redis高级特性">Redis高级特性</h2><h3 id="过期时间-TTL">过期时间(TTL)</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 设置过期时间</span></span><br><span class="line">SETEX session_token 3600 <span class="string">&quot;abc123&quot;</span>  <span class="comment"># 1小时后过期</span></span><br><span class="line">EXPIRE name 60                     <span class="comment"># 60秒后过期</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看剩余时间</span></span><br><span class="line">TTL name</span><br><span class="line">PTTL name  <span class="comment"># 毫秒级</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 移除过期时间</span></span><br><span class="line">PERSIST name</span><br></pre></td></tr></table></figure><h3 id="事务">事务</h3><p>Redis支持简单的事务功能：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 开启事务</span></span><br><span class="line">MULTI</span><br><span class="line">SET key1 <span class="string">&quot;value1&quot;</span></span><br><span class="line">INCR counter</span><br><span class="line">SET key2 <span class="string">&quot;value2&quot;</span></span><br><span class="line">EXEC  <span class="comment"># 执行事务</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 取消事务</span></span><br><span class="line">DISCARD</span><br></pre></td></tr></table></figure><h3 id="发布订阅">发布订阅</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 订阅频道</span></span><br><span class="line">SUBSCRIBE news</span><br><span class="line"></span><br><span class="line"><span class="comment"># 发布消息</span></span><br><span class="line">PUBLISH news <span class="string">&quot;Hello Redis subscribers!&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用模式匹配订阅</span></span><br><span class="line">PSUBSCRIBE news.*</span><br></pre></td></tr></table></figure><h2 id="Redis性能优化">Redis性能优化</h2><h3 id="内存优化">内存优化</h3><ol><li><strong>使用合适的数据结构</strong>：根据场景选择最优的数据类型</li><li><strong>设置合理的过期时间</strong>：避免无用数据长期占用内存</li><li><strong>使用内存压缩</strong>：启用压缩节省内存</li></ol><h3 id="连接优化">连接优化</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看连接数</span></span><br><span class="line">INFO clients</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置最大连接数</span></span><br><span class="line">CONFIG SET maxclients 10000</span><br></pre></td></tr></table></figure><h3 id="持久化配置">持久化配置</h3><h4 id="RDB配置">RDB配置</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># redis.conf</span></span><br><span class="line">save 900 1      <span class="comment"># 900秒内有1个key变化就保存</span></span><br><span class="line">save 300 10     <span class="comment"># 300秒内有10个key变化就保存</span></span><br><span class="line">save 60 10000   <span class="comment"># 60秒内有10000个key变化就保存</span></span><br></pre></td></tr></table></figure><h4 id="AOF配置">AOF配置</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># redis.conf</span></span><br><span class="line">appendonly <span class="built_in">yes</span></span><br><span class="line">appendfsync everysec  <span class="comment"># 每秒同步一次</span></span><br></pre></td></tr></table></figure><h2 id="实际应用场景">实际应用场景</h2><h3 id="1-缓存系统">1. 缓存系统</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> redis</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"></span><br><span class="line"><span class="comment"># 连接Redis</span></span><br><span class="line">r = redis.Redis(host=<span class="string">&#x27;localhost&#x27;</span>, port=<span class="number">6379</span>, db=<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_user_profile</span>(<span class="params">user_id</span>):</span><br><span class="line">    cache_key = <span class="string">f&quot;user:<span class="subst">&#123;user_id&#125;</span>&quot;</span></span><br><span class="line">    cached = r.get(cache_key)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> cached:</span><br><span class="line">        <span class="keyword">return</span> json.loads(cached)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 从数据库获取</span></span><br><span class="line">    user_data = fetch_from_database(user_id)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 缓存1小时</span></span><br><span class="line">    r.setex(cache_key, <span class="number">3600</span>, json.dumps(user_data))</span><br><span class="line">    <span class="keyword">return</span> user_data</span><br></pre></td></tr></table></figure><h3 id="2-计数器">2. 计数器</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 文章阅读量统计</span></span><br><span class="line">r.incr(<span class="string">&quot;article:1000:views&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取阅读量</span></span><br><span class="line">views = r.get(<span class="string">&quot;article:1000:views&quot;</span>)</span><br></pre></td></tr></table></figure><h3 id="3-会话存储">3. 会话存储</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 存储用户会话</span></span><br><span class="line">session_id = <span class="string">&quot;sess_abc123&quot;</span></span><br><span class="line">session_data = &#123;</span><br><span class="line">    <span class="string">&quot;user_id&quot;</span>: <span class="number">1000</span>,</span><br><span class="line">    <span class="string">&quot;username&quot;</span>: <span class="string">&quot;alice&quot;</span>,</span><br><span class="line">    <span class="string">&quot;login_time&quot;</span>: <span class="string">&quot;2024-12-19 14:30:00&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">r.setex(session_id, <span class="number">1800</span>, json.dumps(session_data))  <span class="comment"># 30分钟过期</span></span><br></pre></td></tr></table></figure><h3 id="4-排行榜">4. 排行榜</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 游戏排行榜</span></span><br><span class="line">r.zadd(<span class="string">&quot;game_leaderboard&quot;</span>, &#123;<span class="string">&quot;Alice&quot;</span>: <span class="number">1500</span>, <span class="string">&quot;Bob&quot;</span>: <span class="number">1200</span>, <span class="string">&quot;Charlie&quot;</span>: <span class="number">1800</span>&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取前10名</span></span><br><span class="line">top_players = r.zrevrange(<span class="string">&quot;game_leaderboard&quot;</span>, <span class="number">0</span>, <span class="number">9</span>, withscores=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><h2 id="Redis监控和维护">Redis监控和维护</h2><h3 id="常用监控命令">常用监控命令</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看Redis信息</span></span><br><span class="line">INFO</span><br><span class="line">INFO memory</span><br><span class="line">INFO stats</span><br><span class="line">INFO replication</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看慢查询</span></span><br><span class="line">SLOWLOG GET 10</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看客户端连接</span></span><br><span class="line">CLIENT LIST</span><br></pre></td></tr></table></figure><h3 id="性能测试">性能测试</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用redis-benchmark测试性能</span></span><br><span class="line">redis-benchmark -q -n 100000</span><br><span class="line"></span><br><span class="line"><span class="comment"># 测试指定命令</span></span><br><span class="line">redis-benchmark -t <span class="built_in">set</span>,get -n 100000</span><br></pre></td></tr></table></figure><h2 id="最佳实践总结">最佳实践总结</h2><ol><li><strong>键名设计</strong>：使用冒号分隔的命名空间，如 <code>user:1000:profile</code></li><li><strong>数据大小</strong>：避免存储过大的值（建议&lt;1MB）</li><li><strong>连接管理</strong>：使用连接池，避免频繁创建连接</li><li><strong>错误处理</strong>：做好异常处理，防止缓存雪崩</li><li><strong>监控告警</strong>：设置合理的监控指标和告警阈值</li></ol><h2 id="结语">结语</h2><p>Redis作为一个高性能的内存数据库，在现代应用架构中扮演着重要角色。通过本文的学习，你应该已经掌握了Redis的基本概念、数据类型、常用命令以及实际应用场景。记住，合理使用缓存能够显著提升系统性能，但也要注意缓存一致性、过期策略等问题。</p><p>继续深入学习Redis的集群部署、哨兵模式、Lua脚本等高级特性，将帮助你构建更加健壮和高效的系统。</p><hr><h2 id="参考资料">参考资料</h2><ul><li><a href="https://redis.io/documentation">Redis官方文档</a></li><li><a href="http://redisdoc.com/">Redis命令参考</a></li><li><a href="http://redisbook.com/">Redis设计与实现</a></li><li><a href="https://redis.io/docs/about/">Redis实战</a></li></ul>]]></content>
    
    
    <summary type="html">详细介绍Redis的基本概念、安装配置、数据类型、常用命令以及实际应用场景，帮助开发者快速掌握这个强大的内存数据库。</summary>
    
    
    
    <category term="笔耕问道" scheme="https://blog.adoreorg.cn/categories/%E7%AC%94%E8%80%95%E9%97%AE%E9%81%93/"/>
    
    
    <category term="数据库" scheme="https://blog.adoreorg.cn/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    <category term="redis" scheme="https://blog.adoreorg.cn/tags/redis/"/>
    
    <category term="缓存" scheme="https://blog.adoreorg.cn/tags/%E7%BC%93%E5%AD%98/"/>
    
    <category term="高性能" scheme="https://blog.adoreorg.cn/tags/%E9%AB%98%E6%80%A7%E8%83%BD/"/>
    
  </entry>
  
  <entry>
    <title>Redis实战篇</title>
    <link href="https://blog.adoreorg.cn/posts/2345j34g.html"/>
    <id>https://blog.adoreorg.cn/posts/2345j34g.html</id>
    <published>2025-08-19T06:30:00.000Z</published>
    <updated>2025-08-19T06:30:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="0-实战导读">0. 实战导读</h2><blockquote><p>🎯 <strong>学习目标</strong><br>本教程将带你从Redis基础概念到实际项目应用，通过8个实战案例深入理解Redis的强大功能。</p></blockquote><div class="tabs" id="实战案例"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#实战案例-1">短信登录</button></li><li class="tab"><button type="button" data-href="#实战案例-2">商户查询缓存</button></li><li class="tab"><button type="button" data-href="#实战案例-3">优惠券秒杀</button></li><li class="tab"><button type="button" data-href="#实战案例-4">附近商户</button></li><li class="tab"><button type="button" data-href="#实战案例-5">UV统计</button></li><li class="tab"><button type="button" data-href="#实战案例-6">用户签到</button></li><li class="tab"><button type="button" data-href="#实战案例-7">好友关注</button></li><li class="tab"><button type="button" data-href="#实战案例-8">探店点赞</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="实战案例-1"><p><strong>📱 短信登录</strong><br>使用Redis共享Session实现分布式登录系统，解决传统Session跨域问题</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="实战案例-2"><p><strong>🔍 商户查询缓存</strong><br>深入理解缓存击穿、缓存穿透、缓存雪崩问题，掌握缓存策略和解决方案</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="实战案例-3"><p><strong>🎫 优惠券秒杀</strong><br>Redis计数器 + Lua脚本实现高性能秒杀，分布式锁防止超卖</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="实战案例-4"><p><strong>📍 附近商户</strong><br>利用Redis GEOHash实现地理位置查询，支持附近商家搜索</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="实战案例-5"><p><strong>📊 UV统计</strong><br>使用HyperLogLog进行海量数据去重统计，内存占用极低</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="实战案例-6"><p><strong>✅ 用户签到</strong><br>BitMap实现用户签到功能，节省存储空间</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="实战案例-7"><p><strong>👥 好友关注</strong><br>基于Set集合实现关注、取消关注、共同关注等社交功能</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="实战案例-8"><p><strong>👍 探店点赞</strong><br>List实现点赞列表，SortedSet实现点赞排行榜</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653056228879.png" alt="1653056228879"></p><h2 id="1-短信登录">1. 短信登录</h2><h3 id="1-1、项目架构分析">1.1、项目架构分析</h3><h4 id="1-1-1-系统架构图">1.1.1 系统架构图</h4><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653057872536.png" alt="系统架构图"></p><h4 id="1-1-2-架构设计解析">1.1.2 架构设计解析</h4><div class="note info flat"><p><strong>技术栈</strong></p><ul><li>负载均衡：Nginx</li><li>应用服务：Tomcat集群</li><li>数据存储：MySQL + Redis</li><li>缓存策略：多级缓存架构</li></ul></div><p><strong>请求流程分析：</strong></p><ol><li><p><strong>客户端请求</strong> → <strong>Nginx负载均衡</strong></p><ul><li>Nginx基于七层模型处理HTTP协议</li><li>支持Lua脚本直接访问Redis</li><li>静态资源服务，轻松扛下上万并发</li></ul></li><li><p><strong>负载均衡</strong> → <strong>Tomcat集群</strong></p><ul><li>4核8G Tomcat约处理1000并发</li><li>Nginx负载均衡分流，集群支撑高并发</li><li>动静分离降低Tomcat压力</li></ul></li><li><p><strong>数据访问层</strong></p><ul><li>MySQL企业级：16-32核CPU，32-64G内存</li><li>并发能力：4000-7000 QPS</li><li>Redis集群缓存，降低数据库压力</li></ul></li></ol><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653059409865.png" alt="架构详解"></p><h4 id="1-1-3-项目导入步骤">1.1.3 项目导入步骤</h4><div class="tabs" id="redis"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#redis-1">后端项目</button></li><li class="tab"><button type="button" data-href="#redis-2">前端项目</button></li><li class="tab"><button type="button" data-href="#redis-3">运行项目</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="redis-1"><p><strong>🖥️ 后端项目导入</strong></p><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653060237073.png" alt="后端项目"></p><ol><li>解压后端项目源码</li><li>配置数据库连接</li><li>导入SQL脚本</li><li>启动项目</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redis-2"><p><strong>🌐 前端项目导入</strong></p><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653060337562.png" alt="前端项目"></p><ol><li>解压前端工程</li><li>安装依赖</li><li>配置API接口地址</li><li>启动前端服务</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redis-3"><p><strong>🚀 项目运行</strong></p><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653060588190.png" alt="运行项目"></p><p>访问地址：<code>http://localhost:8080</code></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="1-2-登录流程设计">1.2 登录流程设计</h3><h4 id="1-2-1-登录流程图">1.2.1 登录流程图</h4><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653066208144.png" alt="登录流程"></p><h4 id="1-2-2-详细流程解析">1.2.2 详细流程解析</h4><div class="timeline "><div class='timeline-item'><div class='timeline-item-title'><div class='item-circle'><p>发送验证码</p></div></div><div class='timeline-item-content'><p><strong>📤 发送验证码</strong></p><ol><li><p><strong>手机号校验</strong></p><ul><li>前端提交手机号</li><li>后端验证手机号格式</li><li>格式错误：返回错误信息</li></ul></li><li><p><strong>验证码生成与发送</strong></p><ul><li>生成6位随机验证码</li><li>保存验证码到Session</li><li>调用短信服务发送验证码</li></ul></li></ol></div></div><div class='timeline-item'><div class='timeline-item-title'><div class='item-circle'><p>用户登录</p></div></div><div class='timeline-item-content'><p><strong>🔐 用户登录</strong></p><ol><li><p><strong>参数校验</strong></p><ul><li>校验手机号格式</li><li>校验验证码正确性</li></ul></li><li><p><strong>用户处理</strong></p><ul><li>根据手机号查询用户</li><li>用户不存在：创建新用户</li><li>用户存在：更新登录信息</li></ul></li><li><p><strong>Session管理</strong></p><ul><li>用户信息存入Session</li><li>返回登录成功标识</li></ul></li></ol></div></div><div class='timeline-item'><div class='timeline-item-title'><div class='item-circle'><p>状态校验</p></div></div><div class='timeline-item-content'><p><strong>✅ 状态校验</strong></p><ol><li><p><strong>请求拦截</strong></p><ul><li>从Cookie获取JsessionId</li><li>根据SessionId获取用户信息</li></ul></li><li><p><strong>权限控制</strong></p><ul><li>用户不存在：返回401未授权</li><li>用户存在：信息存入ThreadLocal</li><li>请求放行</li></ul></li></ol></div></div></div><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653066208144.png" alt="1653066208144"></p><h3 id="1-3-核心代码实现">1.3 核心代码实现</h3><h4 id="1-3-1-页面交互流程">1.3.1 页面交互流程</h4><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653067054461.png" alt="页面流程"></p><h4 id="1-3-2-发送验证码功能">1.3.2 发送验证码功能</h4><div class="note success flat"><p><strong>功能说明</strong>：验证手机号格式，生成6位随机验证码并保存到Session</p></div><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">sendCode</span><span class="params">(String phone, HttpSession session)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 校验手机号格式</span></span><br><span class="line">        <span class="keyword">if</span> (RegexUtils.isPhoneInvalid(phone)) &#123;</span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;手机号格式错误！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 生成6位随机验证码</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">code</span> <span class="operator">=</span> RandomUtil.randomNumbers(<span class="number">6</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 保存验证码到Session</span></span><br><span class="line">        session.setAttribute(<span class="string">&quot;code&quot;</span>, code);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 发送验证码（实际项目中调用短信服务）</span></span><br><span class="line">        log.debug(<span class="string">&quot;发送短信验证码成功，验证码：&#123;&#125;&quot;</span>, code);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> Result.ok();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="1-3-3-用户登录功能">1.3.3 用户登录功能</h4><div class="note warning flat"><p><strong>安全提醒</strong>：验证码校验通过后，需要根据手机号查询或创建用户，并将用户信息保存到Session</p></div><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">login</span><span class="params">(LoginFormDTO loginForm, HttpSession session)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 校验手机号格式</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">phone</span> <span class="operator">=</span> loginForm.getPhone();</span><br><span class="line">        <span class="keyword">if</span> (RegexUtils.isPhoneInvalid(phone)) &#123;</span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;手机号格式错误！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 校验验证码</span></span><br><span class="line">        <span class="type">Object</span> <span class="variable">cacheCode</span> <span class="operator">=</span> session.getAttribute(<span class="string">&quot;code&quot;</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">code</span> <span class="operator">=</span> loginForm.getCode();</span><br><span class="line">        <span class="keyword">if</span> (cacheCode == <span class="literal">null</span> || !cacheCode.toString().equals(code)) &#123;</span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;验证码错误&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 根据手机号查询用户</span></span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> query().eq(<span class="string">&quot;phone&quot;</span>, phone).one();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 用户不存在则创建新用户</span></span><br><span class="line">        <span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line">            user = createUserWithPhone(phone);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 5. 保存用户信息到Session</span></span><br><span class="line">        session.setAttribute(<span class="string">&quot;user&quot;</span>, user);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> Result.ok();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> User <span class="title function_">createUserWithPhone</span><span class="params">(String phone)</span> &#123;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">        user.setPhone(phone);</span><br><span class="line">        user.setNickName(<span class="string">&quot;user_&quot;</span> + RandomUtil.randomString(<span class="number">6</span>));</span><br><span class="line">        userService.save(user);</span><br><span class="line">        <span class="keyword">return</span> user;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-4-登录拦截器实现">1.4 登录拦截器实现</h3><h4 id="1-4-1-Tomcat运行原理">1.4.1 Tomcat运行原理</h4><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653068196656.png" alt="Tomcat原理"></p><div class="note info flat"><p><strong>Tomcat线程模型解析</strong></p><ol><li><strong>监听线程</strong>：监听端口，接收客户端连接</li><li><strong>Socket连接</strong>：每对请求-响应创建独立Socket</li><li><strong>线程池</strong>：从线程池获取线程处理请求</li><li><strong>请求处理</strong>：线程转发到Controller→Service→DAO→DB</li><li><strong>响应返回</strong>：处理完成后数据写回客户端Socket</li></ol></div><h4 id="1-4-2-ThreadLocal线程隔离">1.4.2 ThreadLocal线程隔离</h4><div class="note warning flat"><p><strong>ThreadLocal使用要点</strong></p><p>每个用户请求对应Tomcat线程池中的一个线程，通过ThreadLocal实现线程间的数据隔离，确保每个线程操作自己的数据副本。</p></div><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653068874258.png" alt="ThreadLocal原理"></p><h4 id="1-4-3-登录拦截器代码">1.4.3 登录拦截器代码</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoginInterceptor</span> <span class="keyword">implements</span> <span class="title class_">HandlerInterceptor</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(HttpServletRequest request, </span></span><br><span class="line"><span class="params">                           HttpServletResponse response, </span></span><br><span class="line"><span class="params">                           Object handler)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// 1. 获取Session</span></span><br><span class="line">        <span class="type">HttpSession</span> <span class="variable">session</span> <span class="operator">=</span> request.getSession();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 获取Session中的用户信息</span></span><br><span class="line">        <span class="type">Object</span> <span class="variable">user</span> <span class="operator">=</span> session.getAttribute(<span class="string">&quot;user&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 判断用户是否登录</span></span><br><span class="line">        <span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 4. 用户未登录，返回401状态码</span></span><br><span class="line">            response.setStatus(<span class="number">401</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 5. 用户已登录，保存用户信息到ThreadLocal</span></span><br><span class="line">        UserHolder.saveUser((User) user);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 6. 放行请求</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="1-4-4-拦截器配置">1.4.4 拦截器配置</h4><div class="note primary flat"><p><strong>配置说明</strong></p><ul><li><code>order(0)</code>：Token刷新拦截器，优先级最高</li><li><code>order(1)</code>：登录验证拦截器，优先级次之</li><li><code>excludePathPatterns</code>：配置不需要拦截的路径</li></ul></div><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MvcConfig</span> <span class="keyword">implements</span> <span class="title class_">WebMvcConfigurer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addInterceptors</span><span class="params">(InterceptorRegistry registry)</span> &#123;</span><br><span class="line">        <span class="comment">// Token刷新拦截器 - 拦截所有路径</span></span><br><span class="line">        registry.addInterceptor(<span class="keyword">new</span> <span class="title class_">RefreshTokenInterceptor</span>(stringRedisTemplate))</span><br><span class="line">                .addPathPatterns(<span class="string">&quot;/**&quot;</span>)</span><br><span class="line">                .order(<span class="number">0</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 登录拦截器 - 拦截需要登录的路径</span></span><br><span class="line">        registry.addInterceptor(<span class="keyword">new</span> <span class="title class_">LoginInterceptor</span>())</span><br><span class="line">                .excludePathPatterns(</span><br><span class="line">                        <span class="string">&quot;/shop/**&quot;</span>,           <span class="comment">// 店铺信息</span></span><br><span class="line">                        <span class="string">&quot;/voucher/**&quot;</span>,        <span class="comment">// 优惠券</span></span><br><span class="line">                        <span class="string">&quot;/shop-type/**&quot;</span>,      <span class="comment">// 店铺类型</span></span><br><span class="line">                        <span class="string">&quot;/upload/**&quot;</span>,        <span class="comment">// 文件上传</span></span><br><span class="line">                        <span class="string">&quot;/blog/hot&quot;</span>,         <span class="comment">// 热门博客</span></span><br><span class="line">                        <span class="string">&quot;/user/code&quot;</span>,        <span class="comment">// 发送验证码</span></span><br><span class="line">                        <span class="string">&quot;/user/login&quot;</span>        <span class="comment">// 用户登录</span></span><br><span class="line">                )</span><br><span class="line">                .order(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-5-用户信息安全处理">1.5 用户信息安全处理</h3><h4 id="1-5-1-敏感信息隐藏的必要性">1.5.1 敏感信息隐藏的必要性</h4><div class="note danger flat"><p><strong>安全风险警告</strong></p><p>直接返回完整的User实体对象会暴露用户敏感信息（如密码、手机号、邮箱等），存在严重的安全隐患。必须通过DTO对象进行数据脱敏。</p></div><h4 id="1-5-2-解决方案：UserDTO数据传输对象">1.5.2 解决方案：UserDTO数据传输对象</h4><p><strong>UserDTO定义：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserDTO</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line">    <span class="keyword">private</span> String nickName;</span><br><span class="line">    <span class="keyword">private</span> String icon;</span><br><span class="line">    <span class="comment">// 注意：不包含敏感信息如password、phone、email等</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="1-5-3-代码修改">1.5.3 代码修改</h4><p><strong>1. 登录方法修改：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 保存用户信息到Session（使用DTO脱敏）</span></span><br><span class="line">session.setAttribute(<span class="string">&quot;user&quot;</span>, BeanUtil.copyProperties(user, UserDTO.class));</span><br></pre></td></tr></table></figure><p><strong>2. 拦截器修改：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 保存用户信息到ThreadLocal（使用DTO）</span></span><br><span class="line">UserHolder.saveUser((UserDTO) user);</span><br></pre></td></tr></table></figure><p><strong>3. UserHolder工具类：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserHolder</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;UserDTO&gt; tl = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">saveUser</span><span class="params">(UserDTO user)</span> &#123;</span><br><span class="line">        tl.set(user);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> UserDTO <span class="title function_">getUser</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> tl.get();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">removeUser</span><span class="params">()</span> &#123;</span><br><span class="line">        tl.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="1-5-4-安全效果对比">1.5.4 安全效果对比</h4><table><thead><tr><th>返回方式</th><th>包含字段</th><th>安全性</th><th>推荐程度</th></tr></thead><tbody><tr><td>User实体</td><td>id, phone, password, email…</td><td>❌ 危险</td><td>禁止使用</td></tr><tr><td>UserDTO</td><td>id, nickName, icon</td><td>✅ 安全</td><td>强烈推荐</td></tr></tbody></table><h3 id="1-6-Session共享问题">1.6 Session共享问题</h3><h4 id="1-6-1-分布式Session问题分析">1.6.1 分布式Session问题分析</h4><div class="note warning flat"><p><strong>集群环境下的Session一致性挑战</strong></p><p>在分布式系统中，每个Tomcat服务器都有自己的Session存储。当用户请求被分发到不同服务器时，会导致Session数据不一致，用户需要重复登录。</p></div><p><strong>典型问题场景：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">用户请求 → Nginx负载均衡 → Tomcat1 (保存Session)</span><br><span class="line">                    ↓</span><br><span class="line">                   Tomcat2 (无Session数据)</span><br></pre></td></tr></table></figure><p><strong>早期解决方案对比：</strong></p><table><thead><tr><th>方案</th><th>原理</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td>Session复制</td><td>各服务器间同步Session</td><td>实现简单</td><td>网络开销大，性能差</td></tr><tr><td>Session粘连</td><td>固定用户到某台服务器</td><td>无需额外开发</td><td>负载不均，单点故障</td></tr><tr><td>Session集中存储</td><td>使用Redis统一管理</td><td>性能高，可扩展</td><td>需要额外组件</td></tr></tbody></table><h4 id="1-6-2-Redis解决方案">1.6.2 Redis解决方案</h4><div class="note success flat"><p><strong>推荐方案：Redis集中存储</strong></p><p>使用Redis作为Session的集中存储，所有服务器共享同一份Session数据，彻底解决分布式环境下的Session一致性问题。</p></div><p><strong>架构设计：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">用户请求 → Nginx负载均衡 → Tomcat1</span><br><span class="line">                    ↓              ↓</span><br><span class="line">                   Redis ←------ Tomcat2</span><br><span class="line">                   (集中Session存储)</span><br></pre></td></tr></table></figure><p><strong>核心优势：</strong></p><ol><li><strong>数据共享</strong>：所有服务器访问同一份Session数据</li><li><strong>高性能</strong>：Redis内存存储，读写速度快</li><li><strong>可扩展</strong>：支持集群部署，水平扩展</li><li><strong>持久化</strong>：支持数据持久化，防止数据丢失</li></ol><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653069893050.png" alt="1653069893050"></p><h3 id="1-7-Redis代替Session的业务流程">1.7 Redis代替Session的业务流程</h3><h4 id="1-7-1-Redis数据结构选择">1.7.1 Redis数据结构选择</h4><div class="note info flat"><p><strong>数据结构对比分析</strong></p><p>在Redis中存储用户登录信息时，需要根据数据特性和使用场景选择合适的数据结构。</p></div><p><strong>String vs Hash结构对比：</strong></p><table><thead><tr><th>对比维度</th><th>String结构</th><th>Hash结构</th></tr></thead><tbody><tr><td>内存占用</td><td>较高（序列化开销）</td><td>较低（字段独立存储）</td></tr><tr><td>读写性能</td><td>简单快速</td><td>支持字段级操作</td></tr><tr><td>数据更新</td><td>需要整体更新</td><td>支持字段级更新</td></tr><tr><td>适用场景</td><td>简单键值对</td><td>复杂对象存储</td></tr></tbody></table><p><strong>选择建议：</strong></p><ul><li>如果用户信息简单，使用String结构</li><li>如果用户信息复杂，需要频繁更新部分字段，使用Hash结构</li></ul><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653319261433.png" alt="1653319261433"></p><h4 id="1-7-2-Key设计策略">1.7.2 Key设计策略</h4><div class="note warning flat"><p><strong>Key设计原则</strong></p><p>Redis的Key需要满足唯一性和可携带性，同时避免暴露用户敏感信息。</p></div><p><strong>Key设计要点：</strong></p><ol><li><strong>唯一性</strong>：确保每个用户的Key都是唯一的</li><li><strong>可携带性</strong>：Key需要方便在前后端之间传递</li><li><strong>安全性</strong>：避免使用手机号等敏感信息作为Key</li></ol><p><strong>推荐方案：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Key格式：login:token:&#123;随机token&#125;</span><br><span class="line">示例：login:token:a3f2b8c9d1e4f5g6</span><br></pre></td></tr></table></figure><p><strong>Token生成策略：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用UUID生成随机token</span></span><br><span class="line"><span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> UUID.randomUUID().toString(<span class="literal">true</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> LOGIN_USER_KEY + token;</span><br></pre></td></tr></table></figure><h4 id="1-7-3-整体访问流程">1.7.3 整体访问流程</h4><div class="timeline "><div class='timeline-item'><div class='timeline-item-title'><div class='item-circle'></div></div><div class='timeline-item-content'><p>📱 <strong>步骤1：用户登录验证</strong></p><p>用户提交手机号和验证码，系统验证信息一致性</p><p>🔍 <strong>步骤2：用户信息查询</strong></p><p>根据手机号查询用户信息，不存在则创建新用户</p><p>💾 <strong>步骤3：Redis数据存储</strong></p><p>将用户信息保存到Redis，设置合理的过期时间</p><p>🔑 <strong>步骤4：Token返回</strong></p><p>生成随机token作为登录凭证，返回给前端</p><p>🔐 <strong>步骤5：登录状态校验</strong></p><p>后续请求携带token，系统验证Redis中是否存在对应数据</p></div></div></div><h3 id="1-8-基于Redis实现短信登录">1.8 基于Redis实现短信登录</h3><h4 id="1-8-1-实现思路回顾">1.8.1 实现思路回顾</h4><div class="note primary flat"><p><strong>Redis登录方案核心</strong></p><p>将传统的Session存储替换为Redis存储，通过Token机制实现无状态的分布式登录认证。</p></div><p><strong>核心改进点：</strong></p><ol><li><strong>存储位置</strong>：从服务器内存 → Redis缓存</li><li><strong>认证方式</strong>：从SessionID → 随机Token</li><li><strong>数据格式</strong>：从完整对象 → 精简DTO</li><li><strong>有效期管理</strong>：支持灵活的过期时间设置</li></ol><h4 id="1-8-2-核心代码实现">1.8.2 核心代码实现</h4><div class="tabs" id="改进"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#改进-1">登录方法</button></li><li class="tab"><button type="button" data-href="#改进-2">常量定义</button></li><li class="tab"><button type="button" data-href="#改进-3">前端调用</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="改进-1"><p><strong>UserServiceImpl登录方法：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserServiceImpl</span> <span class="keyword">extends</span> <span class="title class_">ServiceImpl</span>&lt;UserMapper, User&gt; <span class="keyword">implements</span> <span class="title class_">IUserService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">login</span><span class="params">(LoginFormDTO loginForm, HttpSession session)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 校验手机号格式</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">phone</span> <span class="operator">=</span> loginForm.getPhone();</span><br><span class="line">        <span class="keyword">if</span> (RegexUtils.isPhoneInvalid(phone)) &#123;</span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;手机号格式错误！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 从Redis获取验证码并校验</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">cacheCode</span> <span class="operator">=</span> stringRedisTemplate.opsForValue()</span><br><span class="line">            .get(LOGIN_CODE_KEY + phone);</span><br><span class="line">        <span class="type">String</span> <span class="variable">code</span> <span class="operator">=</span> loginForm.getCode();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (cacheCode == <span class="literal">null</span> || !cacheCode.equals(code)) &#123;</span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;验证码错误&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. 根据手机号查询用户</span></span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> query().eq(<span class="string">&quot;phone&quot;</span>, phone).one();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 4. 用户不存在则创建新用户</span></span><br><span class="line">        <span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line">            user = createUserWithPhone(phone);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 5. 保存用户信息到Redis</span></span><br><span class="line">        <span class="comment">// 5.1 随机生成token作为登录令牌</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> UUID.randomUUID().toString(<span class="literal">true</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 5.2 将User对象转为HashMap存储</span></span><br><span class="line">        <span class="type">UserDTO</span> <span class="variable">userDTO</span> <span class="operator">=</span> BeanUtil.copyProperties(user, UserDTO.class);</span><br><span class="line">        Map&lt;String, Object&gt; userMap = BeanUtil.beanToMap(userDTO, <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(),</span><br><span class="line">                CopyOptions.create()</span><br><span class="line">                        .setIgnoreNullValue(<span class="literal">true</span>)</span><br><span class="line">                        .setFieldValueEditor((fieldName, fieldValue) -&gt; </span><br><span class="line">                            fieldValue.toString()));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 5.3 存储到Redis</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">tokenKey</span> <span class="operator">=</span> LOGIN_USER_KEY + token;</span><br><span class="line">        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 5.4 设置token有效期（30分钟）</span></span><br><span class="line">        stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 6. 返回token</span></span><br><span class="line">        <span class="keyword">return</span> Result.ok(token);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="改进-2"><p><strong>Redis常量配置：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisConstants</span> &#123;</span><br><span class="line">    <span class="comment">// 验证码相关</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">LOGIN_CODE_KEY</span> <span class="operator">=</span> <span class="string">&quot;login:code:&quot;</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Long</span> <span class="variable">LOGIN_CODE_TTL</span> <span class="operator">=</span> <span class="number">2L</span>; <span class="comment">// 验证码有效期2分钟</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 用户登录相关</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">LOGIN_USER_KEY</span> <span class="operator">=</span> <span class="string">&quot;login:token:&quot;</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Long</span> <span class="variable">LOGIN_USER_TTL</span> <span class="operator">=</span> <span class="number">30L</span>; <span class="comment">// 用户登录有效期30分钟</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 用户签到相关</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">USER_SIGN_KEY</span> <span class="operator">=</span> <span class="string">&quot;sign:&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 商户信息相关</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">CACHE_SHOP_KEY</span> <span class="operator">=</span> <span class="string">&quot;cache:shop:&quot;</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Long</span> <span class="variable">CACHE_SHOP_TTL</span> <span class="operator">=</span> <span class="number">30L</span>; <span class="comment">// 商户信息缓存30分钟</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 互斥锁相关</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">LOCK_SHOP_KEY</span> <span class="operator">=</span> <span class="string">&quot;lock:shop:&quot;</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Long</span> <span class="variable">LOCK_SHOP_TTL</span> <span class="operator">=</span> <span class="number">10L</span>; <span class="comment">// 锁有效期10秒</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="改进-3"><p><strong>前端登录调用：</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 发送验证码</span></span><br><span class="line"><span class="keyword">async</span> <span class="title function_">sendCode</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> request.<span class="title function_">post</span>(<span class="string">&#x27;/user/code&#x27;</span>, &#123;</span><br><span class="line">        <span class="attr">phone</span>: <span class="variable language_">this</span>.<span class="property">phone</span></span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">if</span> (result.<span class="property">code</span> === <span class="number">200</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">$message</span>.<span class="title function_">success</span>(<span class="string">&#x27;验证码发送成功&#x27;</span>);</span><br><span class="line">        <span class="comment">// 开始倒计时</span></span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">startCountdown</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;,</span><br><span class="line"></span><br><span class="line"><span class="comment">// 用户登录</span></span><br><span class="line"><span class="keyword">async</span> <span class="title function_">login</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> request.<span class="title function_">post</span>(<span class="string">&#x27;/user/login&#x27;</span>, &#123;</span><br><span class="line">        <span class="attr">phone</span>: <span class="variable language_">this</span>.<span class="property">phone</span>,</span><br><span class="line">        <span class="attr">code</span>: <span class="variable language_">this</span>.<span class="property">code</span></span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">if</span> (result.<span class="property">code</span> === <span class="number">200</span>) &#123;</span><br><span class="line">        <span class="comment">// 保存token到localStorage</span></span><br><span class="line">        <span class="variable language_">localStorage</span>.<span class="title function_">setItem</span>(<span class="string">&#x27;token&#x27;</span>, result.<span class="property">data</span>);</span><br><span class="line">        <span class="comment">// 设置请求头</span></span><br><span class="line">        request.<span class="property">defaults</span>.<span class="property">headers</span>.<span class="property">common</span>[<span class="string">&#x27;authorization&#x27;</span>] = result.<span class="property">data</span>;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">$message</span>.<span class="title function_">success</span>(<span class="string">&#x27;登录成功&#x27;</span>);</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">$router</span>.<span class="title function_">push</span>(<span class="string">&#x27;/&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="1-9-登录状态刷新优化">1.9 登录状态刷新优化</h3><h4 id="1-9-1-初始方案问题分析">1.9.1 初始方案问题分析</h4><div class="note danger flat"><p><strong>初始方案缺陷</strong></p><p>原方案中拦截器只拦截需要登录验证的路径，导致用户访问无需拦截的路径时，Token无法得到刷新，可能造成Token过期失效。</p></div><p><strong>问题场景：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">用户访问首页 → 无需拦截 → Token不刷新 → Token过期</span><br><span class="line">用户访问个人中心 → 需要拦截 → Token刷新 → 正常访问</span><br></pre></td></tr></table></figure><p><strong>影响分析：</strong></p><ul><li>用户活跃状态下仍可能因Token过期被强制登出</li><li>用户体验差，需要频繁重新登录</li><li>无法准确反映用户的真实活跃状态</li></ul><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653320822964.png" alt="1653320822964"></p><h4 id="1-9-2-双拦截器优化方案">1.9.2 双拦截器优化方案</h4><div class="note success flat"><p><strong>优化思路</strong></p><p>采用双拦截器模式：</p><ul><li><strong>刷新拦截器</strong>：拦截所有路径，负责Token刷新和用户信息加载</li><li><strong>登录拦截器</strong>：只拦截需要登录的路径，负责登录状态校验</li></ul></div><p><strong>架构设计：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">请求进入</span><br><span class="line">    ↓</span><br><span class="line">刷新拦截器（所有路径）→ 刷新Token有效期 → 加载用户信息到ThreadLocal</span><br><span class="line">    ↓</span><br><span class="line">登录拦截器（需登录路径）→ 校验ThreadLocal中用户信息 → 决定是否放行</span><br><span class="line">    ↓</span><br><span class="line">业务处理</span><br></pre></td></tr></table></figure><p><strong>优势分析：</strong></p><ul><li>所有请求都能刷新Token，避免意外过期</li><li>职责分离，代码更清晰</li><li>性能影响最小化</li></ul><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653320764547.png" alt="1653320764547"></p><h4 id="1-9-3-代码实现">1.9.3 代码实现</h4><div class="tabs" id="refresh-token-interceptor-刷新拦截器"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#refresh-token-interceptor-刷新拦截器-1">刷新拦截器</button></li><li class="tab"><button type="button" data-href="#refresh-token-interceptor-刷新拦截器-2">登录拦截器</button></li><li class="tab"><button type="button" data-href="#refresh-token-interceptor-刷新拦截器-3">拦截器配置</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="refresh-token-interceptor-刷新拦截器-1"><p><strong>RefreshTokenInterceptor：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RefreshTokenInterceptor</span> <span class="keyword">implements</span> <span class="title class_">HandlerInterceptor</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">RefreshTokenInterceptor</span><span class="params">(StringRedisTemplate stringRedisTemplate)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.stringRedisTemplate = stringRedisTemplate;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(HttpServletRequest request, </span></span><br><span class="line"><span class="params">                           HttpServletResponse response, </span></span><br><span class="line"><span class="params">                           Object handler)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// 1. 获取请求头中的token</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> request.getHeader(<span class="string">&quot;authorization&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (StrUtil.isBlank(token)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>; <span class="comment">// 无token直接放行</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 基于token获取Redis中的用户信息</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> LOGIN_USER_KEY + token;</span><br><span class="line">        Map&lt;Object, Object&gt; userMap = stringRedisTemplate.opsForHash()</span><br><span class="line">            .entries(key);</span><br><span class="line">            </span><br><span class="line">        <span class="comment">// 3. 判断用户是否存在</span></span><br><span class="line">        <span class="keyword">if</span> (userMap.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>; <span class="comment">// 用户不存在直接放行</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 将查询到的hash数据转为UserDTO</span></span><br><span class="line">        <span class="type">UserDTO</span> <span class="variable">userDTO</span> <span class="operator">=</span> BeanUtil.fillBeanWithMap(userMap, </span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">UserDTO</span>(), <span class="literal">false</span>);</span><br><span class="line">            </span><br><span class="line">        <span class="comment">// 5. 保存用户信息到ThreadLocal</span></span><br><span class="line">        UserHolder.saveUser(userDTO);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 6. 刷新token有效期</span></span><br><span class="line">        stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterCompletion</span><span class="params">(HttpServletRequest request, </span></span><br><span class="line"><span class="params">                              HttpServletResponse response, </span></span><br><span class="line"><span class="params">                              Object handler, Exception ex)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// 移除用户，防止内存泄漏</span></span><br><span class="line">        UserHolder.removeUser();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="refresh-token-interceptor-刷新拦截器-2"><p><strong>LoginInterceptor：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoginInterceptor</span> <span class="keyword">implements</span> <span class="title class_">HandlerInterceptor</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(HttpServletRequest request, </span></span><br><span class="line"><span class="params">                           HttpServletResponse response, </span></span><br><span class="line"><span class="params">                           Object handler)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// 1. 判断是否需要拦截（ThreadLocal中是否有用户）</span></span><br><span class="line">        <span class="keyword">if</span> (UserHolder.getUser() == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 2. 没有登录，设置状态码</span></span><br><span class="line">            response.setStatus(<span class="number">401</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>; <span class="comment">// 拦截</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 有用户，放行</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="refresh-token-interceptor-刷新拦截器-3"><p><strong>MvcConfig配置：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MvcConfig</span> <span class="keyword">implements</span> <span class="title class_">WebMvcConfigurer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> RefreshTokenInterceptor refreshTokenInterceptor;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> LoginInterceptor loginInterceptor;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addInterceptors</span><span class="params">(InterceptorRegistry registry)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 刷新拦截器 - 拦截所有路径，用于刷新token</span></span><br><span class="line">        registry.addInterceptor(refreshTokenInterceptor)</span><br><span class="line">                .addPathPatterns(<span class="string">&quot;/**&quot;</span>)</span><br><span class="line">                .order(<span class="number">0</span>); <span class="comment">// 最高优先级</span></span><br><span class="line">                </span><br><span class="line">        <span class="comment">// 2. 登录拦截器 - 只拦截需要登录的路径</span></span><br><span class="line">        registry.addInterceptor(loginInterceptor)</span><br><span class="line">                .excludePathPatterns(</span><br><span class="line">                    <span class="string">&quot;/user/code&quot;</span>,</span><br><span class="line">                    <span class="string">&quot;/user/login&quot;</span>, </span><br><span class="line">                    <span class="string">&quot;/blog/hot&quot;</span>,</span><br><span class="line">                    <span class="string">&quot;/shop/**&quot;</span>,</span><br><span class="line">                    <span class="string">&quot;/shop-type/**&quot;</span>,</span><br><span class="line">                    <span class="string">&quot;/upload/**&quot;</span>,</span><br><span class="line">                    <span class="string">&quot;/voucher/**&quot;</span></span><br><span class="line">                )</span><br><span class="line">                .order(<span class="number">1</span>); <span class="comment">// 第二优先级</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-商户查询缓存">2. 商户查询缓存</h2><h3 id="2-1-缓存基础概念">2.1 缓存基础概念</h3><h4 id="2-1-1-什么是缓存？">2.1.1 什么是缓存？</h4><div class="note info flat"><p><strong>缓存的本质</strong></p><p>缓存就像避震器一样，为系统提供缓冲保护，防止高频访问对数据库造成冲击。</p></div><p><strong>生活类比：</strong></p><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/%E9%81%BF%E9%9C%87%E5%99%A8.gif" alt="避震器"></p><p>就像越野车的避震器，在崎岖地形中为车体提供保护：</p><ul><li><strong>保护作用</strong>：防止硬着陆对车体造成损害</li><li><strong>缓冲作用</strong>：吸收冲击力，提供平稳体验</li><li><strong>延长寿命</strong>：减少系统组件的磨损</li></ul><p>同样，在实际开发中，系统也需要&quot;避震器&quot;，防止过高的数据访问量冲击系统，导致操作线程无法及时处理信息而瘫痪。</p><h4 id="2-1-2-缓存的技术定义">2.1.2 缓存的技术定义</h4><p><strong>缓存（Cache）</strong>，就是数据交换的<strong>缓冲区</strong>，俗称的缓存就是<strong>缓冲区内的数据</strong>，一般从数据库中获取，存储于本地代码中。</p><div class="tabs" id="缓存技术"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#缓存技术-1">本地缓存</button></li><li class="tab"><button type="button" data-href="#缓存技术-2">分布式缓存</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="缓存技术-1"><p><strong>本地缓存实现：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 高并发场景：ConcurrentHashMap</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ConcurrentHashMap&lt;String, Object&gt; LOCAL_CACHE = </span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 普通场景：HashMap  </span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Map&lt;String, Object&gt; SIMPLE_CACHE = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Guava缓存：功能更完善</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Cache&lt;String, Object&gt; GUAVA_CACHE = </span><br><span class="line">    CacheBuilder.newBuilder()</span><br><span class="line">        .maximumSize(<span class="number">1000</span>)</span><br><span class="line">        .expireAfterWrite(<span class="number">10</span>, TimeUnit.MINUTES)</span><br><span class="line">        .build();</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="缓存技术-2"><p><strong>Redis缓存实现：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> RedisTemplate&lt;String, Object&gt; redisTemplate;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 缓存操作</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setCache</span><span class="params">(String key, Object value, <span class="type">long</span> timeout)</span> &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.MINUTES);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> Object <span class="title function_">getCache</span><span class="params">(String key)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> redisTemplate.opsForValue().get(key);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p><strong>缓存特性：</strong></p><ul><li><strong>Static修饰</strong>：随着类加载而加载到内存中</li><li><strong>Final修饰</strong>：引用关系固定，不用担心赋值导致缓存失效</li><li><strong>内存存储</strong>：读写性能远高于磁盘存储</li></ul><h4 id="2-1-3-为什么要使用缓存？">2.1.3 为什么要使用缓存？</h4><div class="note success flat"><p><strong>缓存的核心价值</strong></p><p>缓存数据存储于内存中，而内存的读写性能远高于磁盘，可以大大降低高并发访问带来的服务器读写压力。</p></div><p><strong>性能对比：</strong></p><table><thead><tr><th>存储介质</th><th>读取速度</th><th>并发能力</th><th>成本</th></tr></thead><tbody><tr><td><strong>内存缓存</strong></td><td>纳秒级</td><td>10万+QPS</td><td>较高</td></tr><tr><td><strong>SSD磁盘</strong></td><td>微秒级</td><td>1万QPS</td><td>中等</td></tr><tr><td><strong>机械磁盘</strong></td><td>毫秒级</td><td>1千QPS</td><td>较低</td></tr></tbody></table><p><strong>业务价值：</strong></p><ol><li><strong>用户体验提升</strong>：页面响应从秒级→毫秒级</li><li><strong>系统稳定性</strong>：防止高并发冲垮数据库</li><li><strong>成本优化</strong>：减少数据库服务器压力</li><li><strong>扩展能力</strong>：支持更高的并发访问量</li></ol><p><strong>数据规模挑战：</strong></p><p>实际开发中，企业数据量从几十万到几千万不等，如果没有缓存作为&quot;避震器&quot;，系统几乎无法承受高并发访问。</p><div class="note warning flat"><p><strong>缓存的代价</strong></p><p>缓存技术虽然强大，但也会增加代码复杂度和运维成本，需要权衡使用。</p></div><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/image-20220523214414123.png" alt=""></p><h4 id="2-1-4-如何使用缓存？">2.1.4 如何使用缓存？</h4><div class="note info flat"><p><strong>多级缓存架构</strong></p><p>在实际开发中，会构建多级缓存体系来最大化系统性能，每一级缓存都有其特定的作用和使用场景。</p></div><p><strong>多级缓存层次：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">用户请求</span><br><span class="line">    ↓</span><br><span class="line">浏览器缓存 (客户端缓存)</span><br><span class="line">    ↓</span><br><span class="line">CDN缓存 (边缘节点缓存)</span><br><span class="line">    ↓</span><br><span class="line">应用层缓存 (Tomcat本地缓存 + Redis分布式缓存)</span><br><span class="line">    ↓</span><br><span class="line">数据库缓存 (Buffer Pool)</span><br><span class="line">    ↓</span><br><span class="line">CPU缓存 (L1/L2/L3)</span><br></pre></td></tr></table></figure><div class="tabs" id="缓存使用"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#缓存使用-1">浏览器缓存</button></li><li class="tab"><button type="button" data-href="#缓存使用-2">应用层缓存</button></li><li class="tab"><button type="button" data-href="#缓存使用-3">数据库缓存</button></li><li class="tab"><button type="button" data-href="#缓存使用-4">CPU缓存</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="缓存使用-1"><p><strong>浏览器缓存：</strong></p><ul><li><strong>存储位置</strong>：用户浏览器本地</li><li><strong>控制方式</strong>：HTTP头信息控制</li><li><strong>典型应用</strong>：静态资源缓存（CSS、JS、图片）</li><li><strong>有效期</strong>：通过Cache-Control、Expires设置</li></ul><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">Cache-Control</span><span class="punctuation">: </span>max-age=3600</span><br><span class="line"><span class="attribute">Expires</span><span class="punctuation">: </span>Wed, 21 Oct 2025 07:28:00 GMT</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="缓存使用-2"><p><strong>应用层缓存：</strong></p><ul><li><strong>本地缓存</strong>：Tomcat JVM内存中的Map结构</li><li><strong>分布式缓存</strong>：Redis集群存储</li><li><strong>使用场景</strong>：热点数据、会话信息、配置数据</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 本地缓存</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Map&lt;String, Object&gt; localCache = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Redis缓存</span></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> RedisTemplate&lt;String, Object&gt; redisTemplate;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="缓存使用-3"><p><strong>数据库缓存：</strong></p><ul><li><strong>Buffer Pool</strong>：InnoDB存储引擎的缓冲池</li><li><strong>查询缓存</strong>：MySQL查询结果缓存（8.0已废弃）</li><li><strong>作用</strong>：减少磁盘IO，提升查询性能</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 查看Buffer Pool状态</span></span><br><span class="line"><span class="keyword">SHOW</span> ENGINE INNODB STATUS\G</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="缓存使用-4"><p><strong>CPU缓存：</strong></p><ul><li><strong>L1缓存</strong>：指令缓存和数据缓存，32-64KB</li><li><strong>L2缓存</strong>：256-512KB，速度次于L1</li><li><strong>L3缓存</strong>：多核心共享，8-32MB</li></ul><p><strong>缓存层级对比：</strong></p><table><thead><tr><th>缓存级别</th><th>容量</th><th>访问延迟</th><th>命中率</th></tr></thead><tbody><tr><td>L1</td><td>32-64KB</td><td>1-2ns</td><td>80%+</td></tr><tr><td>L2</td><td>256-512KB</td><td>3-5ns</td><td>90%+</td></tr><tr><td>L3</td><td>8-32MB</td><td>10-20ns</td><td>95%+</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/image-20220523212915666.png" alt=""></p><h3 id="2-2-商户缓存实现">2.2 商户缓存实现</h3><h4 id="2-2-1-业务场景分析">2.2.1 业务场景分析</h4><div class="note info flat"><p><strong>性能瓶颈识别</strong></p><p>在查询商户信息时，如果直接操作数据库，当并发量增大时，数据库压力会急剧上升，查询性能会显著下降。</p></div><p><strong>原始代码：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryShopById</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long id)</span> &#123;</span><br><span class="line">    <span class="comment">// 直接查询数据库 - 性能瓶颈</span></span><br><span class="line">    <span class="keyword">return</span> shopService.queryById(id);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>性能问题：</strong></p><ul><li>每次查询都要访问数据库</li><li>数据库连接资源有限</li><li>高并发下数据库成为瓶颈</li><li>相同数据重复查询浪费资源</li></ul><h4 id="2-2-2-缓存架构设计">2.2.2 缓存架构设计</h4><div class="note success flat"><p><strong>标准缓存模式</strong></p><p>采用Cache-Aside模式：查询前先查缓存，缓存命中直接返回，未命中查询数据库并写入缓存。</p></div><p><strong>缓存流程图：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">用户请求</span><br><span class="line">    ↓</span><br><span class="line">查询Redis缓存</span><br><span class="line">    ↓</span><br><span class="line">缓存命中？→ 是 → 直接返回缓存数据</span><br><span class="line">    ↓ 否</span><br><span class="line">查询MySQL数据库</span><br><span class="line">    ↓</span><br><span class="line">写入Redis缓存</span><br><span class="line">    ↓</span><br><span class="line">返回查询结果</span><br></pre></td></tr></table></figure><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653322097736.png" alt="1653322097736"></p><h4 id="2-2-3-代码实现">2.2.3 代码实现</h4><div class="note primary flat"><p><strong>实现思路</strong></p><p>代码逻辑：如果缓存命中则直接返回，如果缓存未命中则查询数据库，然后将结果写入Redis缓存。</p></div><div class="tabs" id="缓存"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#缓存-1">Service层实现</button></li><li class="tab"><button type="button" data-href="#缓存-2">Controller层</button></li><li class="tab"><button type="button" data-href="#缓存-3">Redis常量</button></li><li class="tab"><button type="button" data-href="#缓存-4">测试验证</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="缓存-1"><p><strong>ShopServiceImpl：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ShopServiceImpl</span> <span class="keyword">extends</span> <span class="title class_">ServiceImpl</span>&lt;ShopMapper, Shop&gt; <span class="keyword">implements</span> <span class="title class_">IShopService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">queryShopById</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 从Redis查询商户缓存</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> CACHE_SHOP_KEY + id;</span><br><span class="line">        <span class="type">String</span> <span class="variable">shopJson</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(key);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 判断是否存在</span></span><br><span class="line">        <span class="keyword">if</span> (StrUtil.isNotBlank(shopJson)) &#123;</span><br><span class="line">            <span class="comment">// 3. 存在，直接返回</span></span><br><span class="line">            <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> JSONUtil.toBean(shopJson, Shop.class);</span><br><span class="line">            <span class="keyword">return</span> Result.ok(shop);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 不存在，根据id查询数据库</span></span><br><span class="line">        <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> getById(id);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 5. 不存在，返回错误</span></span><br><span class="line">        <span class="keyword">if</span> (shop == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;店铺不存在！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 6. 存在，写入Redis</span></span><br><span class="line">        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), </span><br><span class="line">            CACHE_SHOP_TTL, TimeUnit.MINUTES);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 7. 返回</span></span><br><span class="line">        <span class="keyword">return</span> Result.ok(shop);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="缓存-2"><p><strong>ShopController：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/shop&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ShopController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">queryShop</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long id)</span> &#123;</span><br><span class="line">        <span class="comment">// 调用Service层的缓存查询方法</span></span><br><span class="line">        <span class="keyword">return</span> shopService.queryShopById(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="缓存-3"><p><strong>缓存常量配置：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisConstants</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">CACHE_SHOP_KEY</span> <span class="operator">=</span> <span class="string">&quot;cache:shop:&quot;</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Long</span> <span class="variable">CACHE_SHOP_TTL</span> <span class="operator">=</span> <span class="number">30L</span>; <span class="comment">// 30分钟</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="缓存-4"><p><strong>性能测试对比：</strong></p><table><thead><tr><th>查询方式</th><th>平均响应时间</th><th>QPS</th><th>数据库压力</th></tr></thead><tbody><tr><td>直接查数据库</td><td>150ms</td><td>500</td><td>高</td></tr><tr><td>Redis缓存</td><td>5ms</td><td>10,000+</td><td>低</td></tr><tr><td>性能提升</td><td><strong>30倍</strong></td><td><strong>20倍</strong></td><td><strong>大幅降低</strong></td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653322190155.png" alt="1653322190155"></p><h3 id="2-3-缓存更新策略">2.3 缓存更新策略</h3><h4 id="2-3-1-缓存更新策略概述">2.3.1 缓存更新策略概述</h4><div class="note info flat"><p><strong>缓存更新的必要性</strong></p><p>内存资源宝贵，当缓存数据过多时需要合理的更新策略来保证系统性能和数据一致性。</p></div><p><strong>三大缓存更新策略：</strong></p><div class="tabs" id="缓存更新策略"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#缓存更新策略-1">内存淘汰策略</button></li><li class="tab"><button type="button" data-href="#缓存更新策略-2">超时剔除策略</button></li><li class="tab"><button type="button" data-href="#缓存更新策略-3">主动更新策略</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="缓存更新策略-1"><p><strong>内存淘汰机制</strong></p><p>当Redis内存达到<code>max-memory</code>设置的上限时，自动触发内存淘汰机制，根据配置的淘汰策略删除部分数据。</p><p><strong>常见淘汰策略：</strong></p><table><thead><tr><th>策略</th><th>描述</th><th>适用场景</th></tr></thead><tbody><tr><td>noeviction</td><td>不淘汰，返回错误</td><td>数据不能丢失</td></tr><tr><td>allkeys-lru</td><td>所有key中淘汰最近最少使用</td><td>缓存应用</td></tr><tr><td>volatile-lru</td><td>设置了过期时间的key中淘汰最近最少使用</td><td>混合数据</td></tr><tr><td>allkeys-random</td><td>随机淘汰所有key</td><td>测试环境</td></tr><tr><td>volatile-ttl</td><td>淘汰即将过期的key</td><td>临时数据</td></tr></tbody></table><p><strong>配置示例：</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Redis配置文件</span></span><br><span class="line">maxmemory 2gb</span><br><span class="line">maxmemory-policy allkeys-lru</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="缓存更新策略-2"><p><strong>TTL过期机制</strong></p><p>为缓存数据设置合理的过期时间（TTL），Redis会自动删除过期的数据。</p><p><strong>TTL设置原则：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 业务数据缓存 - 30分钟</span></span><br><span class="line">stringRedisTemplate.opsForValue().set(key, value, <span class="number">30</span>, TimeUnit.MINUTES);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 热点数据缓存 - 1小时  </span></span><br><span class="line">stringRedisTemplate.opsForValue().set(key, value, <span class="number">1</span>, TimeUnit.HOURS);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 配置数据缓存 - 24小时</span></span><br><span class="line">stringRedisTemplate.opsForValue().set(key, value, <span class="number">24</span>, TimeUnit.HOURS);</span><br></pre></td></tr></table></figure><p><strong>TTL设计要点：</strong></p><ul><li>热点数据：TTL较短，保证数据新鲜度</li><li>冷数据：TTL较长，减少数据库压力</li><li>业务数据：根据业务需求设置合理TTL</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="缓存更新策略-3"><p><strong>手动更新机制</strong></p><p>在数据变更时主动删除或更新缓存，保证缓存与数据库的数据一致性。</p><p><strong>更新时机：</strong></p><ul><li>数据新增时：写入缓存</li><li>数据修改时：删除缓存</li><li>数据删除时：删除缓存</li></ul><p><strong>代码示例：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 更新商户信息</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">updateShop</span><span class="params">(Shop shop)</span> &#123;</span><br><span class="line">    <span class="type">Long</span> <span class="variable">id</span> <span class="operator">=</span> shop.getId();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1. 更新数据库</span></span><br><span class="line">    updateById(shop);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 删除缓存（主动更新）</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> CACHE_SHOP_KEY + id;</span><br><span class="line">    stringRedisTemplate.delete(key);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> Result.ok();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653322506393.png" alt="1653322506393"></p><h4 id="2-3-2-数据库缓存不一致解决方案">2.3.2 数据库缓存不一致解决方案</h4><div class="note warning flat"><p><strong>一致性问题分析</strong></p><p>缓存数据源来自数据库，而数据库数据会发生变化。当数据库数据发生变化而缓存未同步时，就会产生一致性问题，用户使用过时数据会影响业务正确性。</p></div><p><strong>三大解决方案对比：</strong></p><div class="tabs" id="一致性问题解决方案"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#一致性问题解决方案-1">Cache Aside Pattern</button></li><li class="tab"><button type="button" data-href="#一致性问题解决方案-2">Read/Write Through</button></li><li class="tab"><button type="button" data-href="#一致性问题解决方案-3">Write Behind Caching</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="一致性问题解决方案-1"><p><strong>旁路缓存模式（推荐）</strong></p><p>缓存调用者在更新数据库后手动更新缓存，也称为双写方案。</p><p><strong>实现流程：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">查询操作：</span><br><span class="line">缓存命中 → 直接返回</span><br><span class="line">缓存未命中 → 查询数据库 → 写入缓存 → 返回结果</span><br><span class="line"></span><br><span class="line">更新操作：</span><br><span class="line">更新数据库 → 删除缓存</span><br></pre></td></tr></table></figure><p><strong>优点：</strong></p><ul><li>✅ 实现简单，控制灵活</li><li>✅ 数据一致性较好</li><li>✅ 适合读多写少场景</li></ul><p><strong>缺点：</strong></p><ul><li>❌ 需要开发者手动维护</li><li>❌ 存在短暂不一致窗口</li></ul><p><strong>适用场景：</strong></p><ul><li>读多写少的业务系统</li><li>对数据一致性要求较高</li><li>开发团队有较强技术能力</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="一致性问题解决方案-2"><p><strong>读写穿透模式</strong></p><p>由缓存系统本身完成数据库与缓存的同步操作。</p><p><strong>实现流程：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">查询操作：</span><br><span class="line">缓存未命中 → 缓存系统查询数据库 → 写入缓存 → 返回结果</span><br><span class="line"></span><br><span class="line">更新操作：</span><br><span class="line">更新缓存 → 缓存系统同步更新数据库</span><br></pre></td></tr></table></figure><p><strong>优点：</strong></p><ul><li>✅ 对应用透明，简化开发</li><li>✅ 缓存系统统一管理</li><li>✅ 数据一致性较好</li></ul><p><strong>缺点：</strong></p><ul><li>❌ 实现复杂，需要缓存系统支持</li><li>❌ 性能开销较大</li><li>❌ 强依赖缓存系统稳定性</li></ul><p><strong>适用场景：</strong></p><ul><li>大型分布式系统</li><li>有专业缓存中间件支持</li><li>对开发简化要求较高</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="一致性问题解决方案-3"><p><strong>异步写回模式</strong></p><p>调用者只操作缓存，其他线程异步处理数据库，实现最终一致性。</p><p><strong>实现流程：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">查询操作：</span><br><span class="line">直接查询缓存</span><br><span class="line"></span><br><span class="line">更新操作：</span><br><span class="line">更新缓存 → 异步线程批量写入数据库</span><br></pre></td></tr></table></figure><p><strong>优点：</strong></p><ul><li>✅ 写入性能极高</li><li>✅ 数据库压力小</li><li>✅ 适合写密集型应用</li></ul><p><strong>缺点：</strong></p><ul><li>❌ 数据一致性差</li><li>❌ 实现复杂度高</li><li>❌ 可能丢失数据</li></ul><p><strong>适用场景：</strong></p><ul><li>写操作远多于读操作</li><li>对数据一致性要求不高</li><li>允许短暂数据丢失</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p><strong>方案选择建议：</strong></p><table><thead><tr><th>方案</th><th>一致性</th><th>性能</th><th>复杂度</th><th>适用场景</th></tr></thead><tbody><tr><td>Cache Aside</td><td>⭐⭐⭐</td><td>⭐⭐⭐</td><td>⭐⭐</td><td>读多写少</td></tr><tr><td>Read/Write Through</td><td>⭐⭐⭐⭐</td><td>⭐⭐</td><td>⭐⭐⭐⭐</td><td>大型系统</td></tr><tr><td>Write Behind</td><td>⭐⭐</td><td>⭐⭐⭐⭐</td><td>⭐⭐⭐⭐⭐</td><td>写密集型</td></tr></tbody></table><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653322857620.png" alt="1653322857620"></p><h4 id="2-3-3-最佳实践方案选择">2.3.3 最佳实践方案选择</h4><div class="note success flat"><p><strong>推荐方案：Cache Aside + 删除缓存策略</strong></p><p>综合考虑一致性、性能和实现复杂度，推荐使用Cache Aside模式配合删除缓存策略。</p></div><p><strong>核心设计原则：</strong></p><div class="tabs" id="删除缓存-vs-更新缓存"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#删除缓存-vs-更新缓存-1">删除缓存 vs 更新缓存</button></li><li class="tab"><button type="button" data-href="#删除缓存-vs-更新缓存-2">操作顺序选择</button></li><li class="tab"><button type="button" data-href="#删除缓存-vs-更新缓存-3">事务保证</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="删除缓存-vs-更新缓存-1"><p><strong>删除缓存策略（推荐）</strong></p><p>更新数据库时让缓存失效，查询时再更新缓存。</p><p><strong>实现流程：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">更新操作：</span><br><span class="line">1. 更新数据库</span><br><span class="line">2. 删除缓存</span><br><span class="line">3. 等待下次查询时重新加载</span><br></pre></td></tr></table></figure><p><strong>优势分析：</strong></p><ul><li>✅ <strong>避免无效写操作</strong>：多次更新只需一次删除</li><li>✅ <strong>简化实现逻辑</strong>：无需考虑缓存数据格式转换</li><li>✅ <strong>降低并发问题</strong>：减少缓存与数据库不一致时间窗口</li></ul><p><strong>对比表格：</strong></p><table><thead><tr><th>策略</th><th>写操作次数</th><th>实现复杂度</th><th>一致性风险</th><th>推荐度</th></tr></thead><tbody><tr><td>更新缓存</td><td>每次更新都写缓存</td><td>高（需处理数据转换）</td><td>高（并发更新冲突）</td><td>⭐⭐</td></tr><tr><td>删除缓存</td><td>仅删除，查询时重建</td><td>低（简单删除）</td><td>低（重建时数据最新）</td><td>⭐⭐⭐⭐⭐</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="删除缓存-vs-更新缓存-2"><p><strong>先操作数据库，再删除缓存（推荐）</strong></p><p>确保数据库操作成功后再删除缓存，避免缓存删除后数据库更新失败的情况。</p><p><strong>时序分析：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">线程1：更新数据库 → 删除缓存</span><br><span class="line">线程2：查询缓存未命中 → 查询数据库 → 写入缓存</span><br></pre></td></tr></table></figure><p><strong>并发安全性：</strong></p><ul><li>即使线程2在线程1删除缓存后查询数据库，获取的也是最新的数据</li><li>避免了&quot;删除缓存→数据库更新失败&quot;导致的数据不一致</li></ul><p><strong>对比分析：</strong></p><table><thead><tr><th>操作顺序</th><th>并发风险</th><th>一致性保证</th><th>实现复杂度</th><th>推荐度</th></tr></thead><tbody><tr><td>先删缓存再更新数据库</td><td>高（其他线程可能写入旧数据）</td><td>差</td><td>高（需额外锁机制）</td><td>⭐⭐</td></tr><tr><td>先更新数据库再删缓存</td><td>低（数据库已更新）</td><td>好</td><td>低</td><td>⭐⭐⭐⭐⭐</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="删除缓存-vs-更新缓存-3"><p><strong>操作原子性保证</strong></p><p>确保缓存与数据库操作同时成功或失败，避免中间状态。</p><p><strong>单体系统事务：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">updateShop</span><span class="params">(Shop shop)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 更新数据库</span></span><br><span class="line">        shopMapper.updateById(shop);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 删除缓存</span></span><br><span class="line">        stringRedisTemplate.delete(CACHE_SHOP_KEY + shop.getId());</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> Result.ok();</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="comment">// 事务回滚，数据库和缓存保持一致</span></span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;更新失败&quot;</span>, e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>分布式系统方案：</strong></p><ul><li><strong>TCC模式</strong>：Try-Confirm-Cancel三阶段提交</li><li><strong>消息队列</strong>：通过消息确保最终一致性</li><li><strong>分布式锁</strong>：保证操作顺序性</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p><strong>最终推荐方案：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">🎯 最佳实践组合：</span><br><span class="line">1. 采用Cache Aside模式</span><br><span class="line">2. 使用删除缓存策略</span><br><span class="line">3. 先更新数据库再删除缓存</span><br><span class="line">4. 单体系统使用事务保证</span><br><span class="line">5. 分布式系统使用消息队列保证最终一致性</span><br></pre></td></tr></table></figure><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653323595206.png" alt="1653323595206"></p><h3 id="2-4-商户缓存双写一致性实现">2.4 商户缓存双写一致性实现</h3><h4 id="2-4-1-实现思路">2.4.1 实现思路</h4><div class="note primary flat"><p><strong>双写一致性要求</strong></p><p>查询时：缓存未命中则查询数据库并写入缓存，设置合理过期时间<br>更新时：先更新数据库，再删除缓存，确保数据一致性</p></div><p><strong>实现流程图：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">查询流程：</span><br><span class="line">用户请求 → 查询Redis缓存 → 命中？→ 是 → 返回缓存数据</span><br><span class="line">                   ↓ 否</span><br><span class="line">               查询MySQL数据库</span><br><span class="line">                   ↓</span><br><span class="line">               写入Redis缓存（带TTL）</span><br><span class="line">                   ↓</span><br><span class="line">               返回查询结果</span><br><span class="line"></span><br><span class="line">更新流程：</span><br><span class="line">用户请求 → 更新MySQL数据库 → 删除Redis缓存 → 返回更新结果</span><br></pre></td></tr></table></figure><h4 id="2-4-2-代码实现">2.4.2 代码实现</h4><div class="tabs" id="查询方法优化"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#查询方法优化-1">查询方法优化</button></li><li class="tab"><button type="button" data-href="#查询方法优化-2">更新方法实现</button></li><li class="tab"><button type="button" data-href="#查询方法优化-3">Controller层调用</button></li><li class="tab"><button type="button" data-href="#查询方法优化-4">一致性验证</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="查询方法优化-1"><p><strong>ShopServiceImpl查询方法：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryShopById</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 从Redis查询商户缓存</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> CACHE_SHOP_KEY + id;</span><br><span class="line">    <span class="type">String</span> <span class="variable">shopJson</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(key);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 判断缓存是否存在</span></span><br><span class="line">    <span class="keyword">if</span> (StrUtil.isNotBlank(shopJson)) &#123;</span><br><span class="line">        <span class="comment">// 3. 存在，直接返回</span></span><br><span class="line">        <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> JSONUtil.toBean(shopJson, Shop.class);</span><br><span class="line">        <span class="keyword">return</span> Result.ok(shop);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4. 缓存未命中，查询数据库</span></span><br><span class="line">    <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> getById(id);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 5. 数据库不存在，返回错误</span></span><br><span class="line">    <span class="keyword">if</span> (shop == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;店铺不存在！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 6. 存在，写入Redis缓存（设置30分钟过期时间）</span></span><br><span class="line">    stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), </span><br><span class="line">        CACHE_SHOP_TTL, TimeUnit.MINUTES);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 7. 返回结果</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok(shop);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="查询方法优化-2"><p><strong>ShopServiceImpl更新方法：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">updateShop</span><span class="params">(Shop shop)</span> &#123;</span><br><span class="line">    <span class="type">Long</span> <span class="variable">id</span> <span class="operator">=</span> shop.getId();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1. 参数校验</span></span><br><span class="line">    <span class="keyword">if</span> (id == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;店铺id不能为空！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 更新数据库</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">updated</span> <span class="operator">=</span> updateById(shop);</span><br><span class="line">    <span class="keyword">if</span> (!updated) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;店铺更新失败！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 删除缓存（保证数据一致性）</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> CACHE_SHOP_KEY + id;</span><br><span class="line">    stringRedisTemplate.delete(key);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4. 返回成功</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="查询方法优化-3"><p><strong>ShopController：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/shop&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ShopController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">queryShop</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long id)</span> &#123;</span><br><span class="line">        <span class="comment">// 查询商户（带缓存）</span></span><br><span class="line">        <span class="keyword">return</span> shopService.queryShopById(id);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PutMapping</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">updateShop</span><span class="params">(<span class="meta">@RequestBody</span> Shop shop)</span> &#123;</span><br><span class="line">        <span class="comment">// 更新商户（保证缓存一致性）</span></span><br><span class="line">        <span class="keyword">return</span> shopService.updateShop(shop);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="查询方法优化-4"><p><strong>测试验证：</strong></p><table><thead><tr><th>测试场景</th><th>预期结果</th><th>实际验证</th></tr></thead><tbody><tr><td>首次查询</td><td>查询数据库，写入缓存</td><td>✅</td></tr><tr><td>二次查询</td><td>直接返回缓存数据</td><td>✅</td></tr><tr><td>数据更新</td><td>更新数据库，删除缓存</td><td>✅</td></tr><tr><td>更新后查询</td><td>重新查询数据库，写入新缓存</td><td>✅</td></tr></tbody></table><p><strong>性能对比：</strong></p><table><thead><tr><th>操作类型</th><th>直接数据库</th><th>带缓存</th><th>性能提升</th></tr></thead><tbody><tr><td>查询操作</td><td>150ms</td><td>5ms</td><td><strong>30倍</strong></td></tr><tr><td>更新操作</td><td>50ms</td><td>55ms</td><td>基本持平</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653325871232.png" alt="1653325871232"></p><p><strong>代码分析：</strong><br>通过采用删除缓存策略解决双写问题，当数据更新后删除缓存，后续查询会从MySQL加载最新数据并重新写入缓存，从而避免数据库和缓存不一致的问题。</p><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653325929549.png" alt="1653325929549"></p><h3 id="2-5-缓存穿透问题解决">2.5 缓存穿透问题解决</h3><h4 id="2-5-1-缓存穿透问题分析">2.5.1 缓存穿透问题分析</h4><div class="note danger flat"><p><strong>缓存穿透定义</strong></p><p>客户端请求的数据在缓存和数据库中都不存在，导致缓存永远无法生效，所有请求都直接打到数据库，可能引发数据库崩溃。</p></div><p><strong>问题场景：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">用户请求ID=99999的商品</span><br><span class="line">    ↓</span><br><span class="line">查询Redis缓存（不存在）</span><br><span class="line">    ↓</span><br><span class="line">查询MySQL数据库（不存在）</span><br><span class="line">    ↓</span><br><span class="line">返回404错误</span><br><span class="line">    ↓</span><br><span class="line">大量恶意请求重复此过程</span><br><span class="line">    ↓</span><br><span class="line">数据库压力剧增，可能崩溃</span><br></pre></td></tr></table></figure><p><strong>风险特征：</strong></p><ul><li>🔴 请求的数据在数据库中不存在</li><li>🔴 缓存无法命中，失去保护作用</li><li>🔴 大量请求直接访问数据库</li><li>🔴 可能被恶意利用进行攻击</li></ul><h4 id="2-5-2-解决方案对比">2.5.2 解决方案对比</h4><p><strong>两大核心解决方案：</strong></p><div class="tabs" id="缓存穿透解决方案"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#缓存穿透解决方案-1">缓存空对象方案</button></li><li class="tab"><button type="button" data-href="#缓存穿透解决方案-2">布隆过滤器方案</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="缓存穿透解决方案-1"><p><strong>实现原理</strong></p><p>当数据库查询结果为空时，仍将空结果缓存到Redis，设置较短的过期时间。</p><p><strong>实现流程：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">用户请求 → 查询缓存未命中 → 查询数据库为空 → 缓存空值 → 返回结果</span><br><span class="line">下次请求 → 查询缓存命中（空值）→ 直接返回，不再访问数据库</span><br></pre></td></tr></table></figure><p><strong>代码示例：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryShopById</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> CACHE_SHOP_KEY + id;</span><br><span class="line">    <span class="type">String</span> <span class="variable">shopJson</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(key);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 命中缓存</span></span><br><span class="line">    <span class="keyword">if</span> (StrUtil.isNotBlank(shopJson)) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="string">&quot;null&quot;</span>.equals(shopJson)) &#123;  <span class="comment">// 判断是否为缓存的空值</span></span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;店铺不存在！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> JSONUtil.toBean(shopJson, Shop.class);</span><br><span class="line">        <span class="keyword">return</span> Result.ok(shop);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 查询数据库</span></span><br><span class="line">    <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> getById(id);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (shop == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 缓存空值，设置2分钟过期</span></span><br><span class="line">        stringRedisTemplate.opsForValue().set(key, <span class="string">&quot;&quot;</span>, <span class="number">2</span>, TimeUnit.MINUTES);</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;店铺不存在！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 缓存正常数据</span></span><br><span class="line">    stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);</span><br><span class="line">    <span class="keyword">return</span> Result.ok(shop);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>方案评估：</strong></p><table><thead><tr><th>维度</th><th>评分</th><th>说明</th></tr></thead><tbody><tr><td>实现复杂度</td><td>⭐⭐</td><td>简单，几行代码即可实现</td></tr><tr><td>内存消耗</td><td>⭐⭐⭐</td><td>需要额外存储空值，但可设置短TTL</td></tr><tr><td>防护效果</td><td>⭐⭐⭐⭐</td><td>有效阻止重复查询不存在数据</td></tr><tr><td>数据一致性</td><td>⭐⭐⭐</td><td>可能存在短暂不一致</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="缓存穿透解决方案-2"><p><strong>实现原理</strong></p><p>使用布隆过滤器在查询前进行预判，过滤掉明显不存在的数据请求。</p><p><strong>数据结构：</strong></p><ul><li>庞大的二进制位数组（BitSet）</li><li>多个哈希函数</li><li>内存占用极低</li></ul><p><strong>实现流程：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">用户请求 → 布隆过滤器判断 → 不存在 → 直接返回</span><br><span class="line">                ↓ 存在</span><br><span class="line">            查询Redis缓存 → 未命中 → 查询数据库</span><br></pre></td></tr></table></figure><p><strong>代码示例：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BloomFilterService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 初始化布隆过滤器</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">initBloomFilter</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 将所有商品ID添加到布隆过滤器</span></span><br><span class="line">        List&lt;Long&gt; allShopIds = shopMapper.selectAllIds();</span><br><span class="line">        <span class="keyword">for</span> (Long id : allShopIds) &#123;</span><br><span class="line">            addToBloomFilter(<span class="string">&quot;shop:bloom:&quot;</span>, id);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 添加到布隆过滤器</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">addToBloomFilter</span><span class="params">(String key, Long id)</span> &#123;</span><br><span class="line">        <span class="type">int</span>[] offset = getBitOffset(id);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i : offset) &#123;</span><br><span class="line">            stringRedisTemplate.opsForValue().setBit(key, i, <span class="literal">true</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 判断是否存在</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">mightExist</span><span class="params">(String key, Long id)</span> &#123;</span><br><span class="line">        <span class="type">int</span>[] offset = getBitOffset(id);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i : offset) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!stringRedisTemplate.opsForValue().getBit(key, i)) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;  <span class="comment">// 一定不存在</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;  <span class="comment">// 可能存在（存在误判）</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>方案评估：</strong></p><table><thead><tr><th>维度</th><th>评分</th><th>说明</th></tr></thead><tbody><tr><td>实现复杂度</td><td>⭐⭐⭐⭐</td><td>需要额外组件，算法复杂</td></tr><tr><td>内存消耗</td><td>⭐⭐⭐⭐⭐</td><td>极低的内存占用</td></tr><tr><td>防护效果</td><td>⭐⭐⭐⭐⭐</td><td>完美阻止不存在数据查询</td></tr><tr><td>误判率</td><td>⭐⭐</td><td>存在误判可能，需权衡配置</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p><strong>方案选择建议：</strong></p><table><thead><tr><th>业务场景</th><th>推荐方案</th><th>理由</th></tr></thead><tbody><tr><td>中小型系统</td><td>缓存空对象</td><td>实现简单，快速上线</td></tr><tr><td>大型高并发系统</td><td>布隆过滤器</td><td>内存优化，防护效果更好</td></tr><tr><td>数据量巨大</td><td>布隆过滤器</td><td>内存占用与数据量无关</td></tr><tr><td>开发资源有限</td><td>缓存空对象</td><td>维护成本低</td></tr></tbody></table><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653326156516.png" alt="1653326156516"></p><h3 id="2-6-缓存穿透编码实现">2.6 缓存穿透编码实现</h3><h4 id="2-6-1-实现思路">2.6.1 实现思路</h4><div class="note warning flat"><p><strong>原始问题</strong></p><p>在原来的逻辑中，如果发现数据在MySQL中不存在，直接返回404，这样会导致缓存穿透问题，大量请求直接到达数据库。</p></div><p><strong>优化思路：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">原逻辑：</span><br><span class="line">查询缓存未命中 → 查询数据库为空 → 直接返回404 → 缓存穿透风险</span><br><span class="line"></span><br><span class="line">新逻辑：</span><br><span class="line">查询缓存未命中 → 查询数据库为空 → 缓存空值 → 返回结果 → 下次直接命中缓存</span><br></pre></td></tr></table></figure><h4 id="2-6-2-代码实现">2.6.2 代码实现</h4><div class="tabs" id="缓存穿透实现"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#缓存穿透实现-1">完整实现</button></li><li class="tab"><button type="button" data-href="#缓存穿透实现-2">工具方法</button></li><li class="tab"><button type="button" data-href="#缓存穿透实现-3">测试验证</button></li><li class="tab"><button type="button" data-href="#缓存穿透实现-4">高级优化</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="缓存穿透实现-1"><p><strong>ShopServiceImpl缓存穿透防护：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryShopById</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 参数校验</span></span><br><span class="line">    <span class="keyword">if</span> (id == <span class="literal">null</span> || id &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;店铺ID非法！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 从Redis查询商户缓存</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> CACHE_SHOP_KEY + id;</span><br><span class="line">    <span class="type">String</span> <span class="variable">shopJson</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(key);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 判断缓存是否存在</span></span><br><span class="line">    <span class="keyword">if</span> (StrUtil.isNotBlank(shopJson)) &#123;</span><br><span class="line">        <span class="comment">// 4. 命中缓存，判断是否为null值（缓存穿透防护）</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="string">&quot;null&quot;</span>.equals(shopJson)) &#123;</span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;店铺不存在！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 5. 正常数据，直接返回</span></span><br><span class="line">        <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> JSONUtil.toBean(shopJson, Shop.class);</span><br><span class="line">        <span class="keyword">return</span> Result.ok(shop);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 6. 缓存未命中，查询数据库</span></span><br><span class="line">    <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> getById(id);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 7. 数据库不存在（缓存穿透防护）</span></span><br><span class="line">    <span class="keyword">if</span> (shop == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 8. 缓存空值，设置较短的过期时间（2分钟）</span></span><br><span class="line">        stringRedisTemplate.opsForValue().set(key, <span class="string">&quot;&quot;</span>, <span class="number">2</span>, TimeUnit.MINUTES);</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;店铺不存在！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 9. 数据库存在，写入Redis缓存</span></span><br><span class="line">    stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), </span><br><span class="line">        CACHE_SHOP_TTL, TimeUnit.MINUTES);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 10. 返回结果</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok(shop);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="缓存穿透实现-2"><p><strong>缓存穿透防护工具类：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CachePenetrationProtection</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 安全的缓存查询方法</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key 缓存key</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> type 返回类型</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> dbFallback 数据库查询函数</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> ttl 过期时间</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> unit 时间单位</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 查询结果</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; T <span class="title function_">safeQuery</span><span class="params">(String key, Class&lt;T&gt; type, </span></span><br><span class="line"><span class="params">                          Supplier&lt;T&gt; dbFallback, </span></span><br><span class="line"><span class="params">                          Long ttl, TimeUnit unit)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 查询缓存</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(key);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 命中缓存</span></span><br><span class="line">        <span class="keyword">if</span> (StrUtil.isNotBlank(json)) &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="string">&quot;null&quot;</span>.equals(json)) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">null</span>;  <span class="comment">// 缓存的空值</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> JSONUtil.toBean(json, type);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 查询数据库</span></span><br><span class="line">        <span class="type">T</span> <span class="variable">result</span> <span class="operator">=</span> dbFallback.get();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 缓存结果（包括空值）</span></span><br><span class="line">        <span class="keyword">if</span> (result == <span class="literal">null</span>) &#123;</span><br><span class="line">            stringRedisTemplate.opsForValue().set(key, <span class="string">&quot;&quot;</span>, <span class="number">2</span>, TimeUnit.MINUTES);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(result), ttl, unit);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="缓存穿透实现-3"><p><strong>防护效果测试：</strong></p><table><thead><tr><th>测试场景</th><th>请求参数</th><th>预期结果</th><th>数据库访问次数</th></tr></thead><tbody><tr><td>正常查询</td><td>id=1</td><td>返回店铺信息</td><td>1次</td></tr><tr><td>不存在查询</td><td>id=99999</td><td>返回&quot;店铺不存在&quot;</td><td>1次</td></tr><tr><td>重复不存在查询</td><td>id=99999</td><td>返回&quot;店铺不存在&quot;</td><td>0次（命中缓存空值）</td></tr><tr><td>非法参数</td><td>id=-1</td><td>返回&quot;店铺ID非法&quot;</td><td>0次（参数校验拦截）</td></tr></tbody></table><p><strong>性能对比：</strong></p><table><thead><tr><th>查询类型</th><th>无防护QPS</th><th>有防护QPS</th><th>数据库压力</th></tr></thead><tbody><tr><td>不存在数据查询</td><td>1000（全部打到DB）</td><td>10000+（缓存拦截）</td><td>降低90%+</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="缓存穿透实现-4"><p><strong>多重防护策略：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/shop&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ShopController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">queryShop</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> String idStr)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 参数格式校验（第一层防护）</span></span><br><span class="line">        <span class="keyword">if</span> (!idStr.matches(<span class="string">&quot;\\d+&quot;</span>)) &#123;</span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;店铺ID格式错误！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">Long</span> <span class="variable">id</span> <span class="operator">=</span> Long.valueOf(idStr);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. ID范围校验（第二层防护）</span></span><br><span class="line">        <span class="keyword">if</span> (id &gt; <span class="number">1000000</span>) &#123;  <span class="comment">// 假设最大ID为100万</span></span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;店铺ID超出范围！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 调用带缓存穿透防护的Service方法</span></span><br><span class="line">        <span class="keyword">return</span> shopService.queryShopById(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653327124561.png" alt="1653327124561"></p><h4 id="2-6-3-总结与最佳实践">2.6.3 总结与最佳实践</h4><p><strong>缓存穿透产生原因：</strong></p><ul><li>用户请求的数据在缓存和数据库中都不存在</li><li>大量请求不断发起，给数据库带来巨大压力</li></ul><p><strong>解决方案完整清单：</strong></p><table><thead><tr><th>防护层级</th><th>解决方案</th><th>实现复杂度</th><th>防护效果</th></tr></thead><tbody><tr><td>应用层</td><td>参数格式校验</td><td>⭐</td><td>基础防护</td></tr><tr><td>应用层</td><td>ID范围限制</td><td>⭐⭐</td><td>中级防护</td></tr><tr><td>缓存层</td><td>缓存空对象</td><td>⭐⭐⭐</td><td>核心防护</td></tr><tr><td>架构层</td><td>布隆过滤器</td><td>⭐⭐⭐⭐⭐</td><td>高级防护</td></tr><tr><td>系统层</td><td>限流熔断</td><td>⭐⭐⭐⭐</td><td>兜底防护</td></tr></tbody></table><p><strong>推荐组合：</strong></p><ul><li>中小型系统：参数校验 + 缓存空对象</li><li>大型系统：参数校验 + 布隆过滤器 + 缓存空对象</li><li>超高并发：全部方案组合使用</li></ul><h3 id="2-7-缓存雪崩问题解决">2.7 缓存雪崩问题解决</h3><h4 id="2-7-1-缓存雪崩问题分析">2.7.1 缓存雪崩问题分析</h4><div class="note danger flat"><p><strong>缓存雪崩</strong>：在同一时段大量的缓存key同时失效或者Redis服务宕机，导致大量请求直接到达数据库，可能瞬间压垮数据库，造成系统级故障。</p></div><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653327884526.png" alt="1653327884526"><br><strong>问题场景</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">用户请求 → 缓存失效 → 请求数据库 → 数据库压力过大 → 数据库崩溃 → 服务不可用</span><br></pre></td></tr></table></figure><p><strong>风险特征</strong>：</p><ul><li>突发性：大量key在同一时间失效</li><li>级联性：缓存失效→数据库压力→系统崩溃</li><li>恢复难：系统恢复后可能再次雪崩</li></ul><h4 id="2-7-2-解决方案对比">2.7.2 解决方案对比</h4><div class="tabs" id="cache-avalanche"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#cache-avalanche-1">过期时间随机化</button></li><li class="tab"><button type="button" data-href="#cache-avalanche-2">Redis集群高可用</button></li><li class="tab"><button type="button" data-href="#cache-avalanche-3">降级限流策略</button></li><li class="tab"><button type="button" data-href="#cache-avalanche-4">多级缓存架构</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="cache-avalanche-1"><p><strong>过期时间随机化</strong></p><p><strong>实现原理</strong>：<br>在基础TTL上添加随机值，分散key的过期时间</p><p><strong>代码实现</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 原TTL为30分钟</span></span><br><span class="line"><span class="type">int</span> <span class="variable">baseTtl</span> <span class="operator">=</span> <span class="number">30</span>;</span><br><span class="line"><span class="comment">// 添加随机值，范围±5分钟</span></span><br><span class="line"><span class="type">int</span> <span class="variable">randomTtl</span> <span class="operator">=</span> baseTtl + ThreadLocalRandom.current().nextInt(-<span class="number">5</span>, <span class="number">6</span>);</span><br><span class="line">stringRedisTemplate.opsForValue().set(key, value, randomTtl, TimeUnit.MINUTES);</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ul><li>实现简单，成本低</li><li>能有效分散过期时间</li><li>对业务无侵入</li></ul><p><strong>劣势</strong>：</p><ul><li>无法应对Redis宕机</li><li>随机范围需要合理设置</li></ul><p><strong>适用场景</strong>：<br>适合大多数业务场景，作为基础防护手段</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="cache-avalanche-2"><p><strong>Redis集群高可用</strong></p><p><strong>实现原理</strong>：<br>通过主从复制、哨兵模式、集群模式提高Redis可用性</p><p><strong>架构对比</strong>：</p><table><thead><tr><th>方案</th><th>可用性</th><th>数据安全</th><th>复杂度</th><th>成本</th></tr></thead><tbody><tr><td>主从复制</td><td>★★☆</td><td>★★☆</td><td>★☆☆</td><td>低</td></tr><tr><td>哨兵模式</td><td>★★★</td><td>★★☆</td><td>★★☆</td><td>中</td></tr><tr><td>Redis Cluster</td><td>★★★</td><td>★★★</td><td>★★★</td><td>高</td></tr></tbody></table><p><strong>配置示例</strong>（哨兵模式）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># sentinel.conf</span></span><br><span class="line">sentinel monitor mymaster 127.0.0.1 6379 2</span><br><span class="line">sentinel down-after-milliseconds mymaster 5000</span><br><span class="line">sentinel failover-timeout mymaster 10000</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ul><li>提供故障自动转移</li><li>数据安全性高</li><li>服务可用性强</li></ul><p><strong>劣势</strong>：</p><ul><li>架构复杂，维护成本高</li><li>需要更多资源投入</li></ul><p><strong>适用场景</strong>：<br>对可用性要求高的核心业务</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="cache-avalanche-3"><p><strong>降级限流策略</strong></p><p><strong>实现原理</strong>：<br>在缓存失效时，通过降级和限流保护数据库</p><p><strong>降级策略</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SentinelResource(value = &quot;getShop&quot;, </span></span><br><span class="line"><span class="meta">    blockHandler = &quot;handleBlock&quot;, </span></span><br><span class="line"><span class="meta">    fallback = &quot;handleFallback&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Shop <span class="title function_">getShop</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="comment">// 正常业务逻辑</span></span><br><span class="line">    <span class="keyword">return</span> shopService.getById(id);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 限流处理</span></span><br><span class="line"><span class="keyword">public</span> Shop <span class="title function_">handleBlock</span><span class="params">(Long id, BlockException ex)</span> &#123;</span><br><span class="line">    log.warn(<span class="string">&quot;接口被限流: &#123;&#125;&quot;</span>, id);</span><br><span class="line">    <span class="keyword">return</span> getDefaultShop(id); <span class="comment">// 返回默认值或缓存数据</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 降级处理</span></span><br><span class="line"><span class="keyword">public</span> Shop <span class="title function_">handleFallback</span><span class="params">(Long id, Throwable ex)</span> &#123;</span><br><span class="line">    log.error(<span class="string">&quot;接口降级: &#123;&#125;&quot;</span>, id, ex);</span><br><span class="line">    <span class="keyword">return</span> getDefaultShop(id); <span class="comment">// 返回兜底数据</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>限流算法</strong>：</p><ul><li>令牌桶算法：平滑流量</li><li>漏桶算法：固定速率处理</li><li>计数器算法：简单直接</li></ul><p><strong>优势</strong>：</p><ul><li>保护系统稳定性</li><li>提供用户体验降级方案</li><li>灵活配置</li></ul><p><strong>劣势</strong>：</p><ul><li>可能损失部分功能</li><li>需要合理设置阈值</li></ul><p><strong>适用场景</strong>：<br>高并发、大数据量场景</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="cache-avalanche-4"><p><strong>多级缓存架构</strong></p><p><strong>架构设计</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">用户请求 → CDN缓存 → Nginx缓存 → 本地缓存 → Redis缓存 → 数据库</span><br></pre></td></tr></table></figure><p><strong>实现方案</strong>：</p><table><thead><tr><th>缓存层级</th><th>技术选型</th><th>响应时间</th><th>容量</th><th>成本</th></tr></thead><tbody><tr><td>CDN缓存</td><td>CloudFlare/阿里云CDN</td><td>10-50ms</td><td>巨大</td><td>高</td></tr><tr><td>Nginx缓存</td><td>proxy_cache</td><td>1-5ms</td><td>中等</td><td>低</td></tr><tr><td>本地缓存</td><td>Caffeine/Guava</td><td>0.1-1ms</td><td>小</td><td>极低</td></tr><tr><td>Redis缓存</td><td>Redis Cluster</td><td>1-10ms</td><td>大</td><td>中</td></tr></tbody></table><p><strong>本地缓存实现</strong>（Caffeine）：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 本地缓存配置</span></span><br><span class="line">LoadingCache&lt;String, Shop&gt; localCache = Caffeine.newBuilder()</span><br><span class="line">    .maximumSize(<span class="number">10000</span>)</span><br><span class="line">    .expireAfterWrite(<span class="number">5</span>, TimeUnit.MINUTES)</span><br><span class="line">    .build(key -&gt; &#123;</span><br><span class="line">        <span class="comment">// 缓存未命中时从Redis加载</span></span><br><span class="line">        <span class="keyword">return</span> getFromRedis(key);</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> Shop <span class="title function_">queryShop</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="comment">// 优先查询本地缓存</span></span><br><span class="line">    <span class="keyword">return</span> localCache.get(CACHE_SHOP_KEY + id);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ul><li>多层次保护，容错性强</li><li>响应速度快，用户体验好</li><li>各层缓存互补</li></ul><p><strong>劣势</strong>：</p><ul><li>架构复杂，实现难度大</li><li>数据一致性挑战</li><li>运维成本高</li></ul><p><strong>适用场景</strong>：<br>大型互联网应用，对性能和可用性要求极高</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h4 id="2-7-3-最佳实践方案">2.7.3 最佳实践方案</h4><div class="note success flat"><p><strong>推荐方案</strong>：过期时间随机化 + Redis集群 + 本地缓存</p></div><p><strong>方案组合</strong>：</p><ol><li><strong>基础层</strong>：过期时间随机化（必做）</li><li><strong>可用层</strong>：Redis哨兵模式或Cluster（推荐）</li><li><strong>加速层</strong>：本地缓存（Caffeine/Guava）</li><li><strong>保护层</strong>：降级限流（Sentinel）</li></ol><p><strong>实施步骤</strong>：</p><ol><li>为所有缓存key添加随机TTL</li><li>部署Redis哨兵模式</li><li>集成本地缓存框架</li><li>配置限流降级规则</li><li>监控和告警设置</li></ol><p><strong>效果预期</strong>：</p><ul><li>缓存雪崩概率降低90%+</li><li>系统可用性达到99.9%+</li><li>响应时间提升50%+</li></ul><h3 id="2-8-缓存击穿问题解决">2.8 缓存击穿问题解决</h3><h4 id="2-8-1-缓存击穿问题分析">2.8.1 缓存击穿问题分析</h4><div class="note danger flat"><p><strong>缓存击穿</strong>：也叫热点Key问题，一个被高并发访问并且缓存重建业务较复杂的key突然失效，无数请求瞬间到达数据库，可能造成数据库崩溃。</p></div><p><strong>问题场景</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">高并发请求 → 热点key失效 → 大量请求数据库 → 数据库压力激增 → 数据库崩溃</span><br></pre></td></tr></table></figure><p><strong>与缓存穿透的区别</strong>：</p><ul><li>缓存穿透：查询不存在的数据</li><li>缓存击穿：热点key突然失效</li><li>缓存雪崩：大量key同时失效</li></ul><h4 id="2-8-2-解决方案对比">2.8.2 解决方案对比</h4><div class="tabs" id="cache-breakdown"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#cache-breakdown-1">互斥锁方案</button></li><li class="tab"><button type="button" data-href="#cache-breakdown-2">逻辑过期方案</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="cache-breakdown-1"><p><strong>互斥锁方案</strong></p><p><strong>实现原理</strong>：<br>通过分布式锁保证只有一个线程去重建缓存，其他线程等待<br><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653328288627.png" alt="1653328288627"><br><strong>流程图</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">请求1：获取锁 → 查询数据库 → 重建缓存 → 释放锁 → 返回数据</span><br><span class="line">请求2：等待锁 → 从缓存获取 → 返回数据</span><br><span class="line">请求3：等待锁 → 从缓存获取 → 返回数据</span><br></pre></td></tr></table></figure><p><strong>代码实现</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Shop <span class="title function_">queryWithMutex</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> CACHE_SHOP_KEY + id;</span><br><span class="line">    <span class="type">String</span> <span class="variable">lockKey</span> <span class="operator">=</span> LOCK_SHOP_KEY + id;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1. 查询缓存</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">shopJson</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(key);</span><br><span class="line">    <span class="keyword">if</span> (StrUtil.isNotBlank(shopJson)) &#123;</span><br><span class="line">        <span class="keyword">return</span> JSONUtil.toBean(shopJson, Shop.class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 获取互斥锁</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> tryLock(lockKey);</span><br><span class="line">    <span class="keyword">if</span> (!isLock) &#123;</span><br><span class="line">        <span class="comment">// 3. 获取锁失败，休眠重试</span></span><br><span class="line">        Thread.sleep(<span class="number">50</span>);</span><br><span class="line">        <span class="keyword">return</span> queryWithMutex(id); <span class="comment">// 递归重试</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 4. 获取锁成功，再次检查缓存（double check）</span></span><br><span class="line">        shopJson = stringRedisTemplate.opsForValue().get(key);</span><br><span class="line">        <span class="keyword">if</span> (StrUtil.isNotBlank(shopJson)) &#123;</span><br><span class="line">            <span class="keyword">return</span> JSONUtil.toBean(shopJson, Shop.class);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 5. 查询数据库并重建缓存</span></span><br><span class="line">        <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> getById(id);</span><br><span class="line">        <span class="keyword">if</span> (shop == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 防止缓存穿透</span></span><br><span class="line">            stringRedisTemplate.opsForValue().set(key, <span class="string">&quot;&quot;</span>, CACHE_NULL_TTL, TimeUnit.MINUTES);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);</span><br><span class="line">        <span class="keyword">return</span> shop;</span><br><span class="line">        </span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="comment">// 6. 释放锁</span></span><br><span class="line">        unlock(lockKey);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ul><li>数据一致性强</li><li>实现相对简单</li><li>无额外内存开销</li></ul><p><strong>劣势</strong>：</p><ul><li>性能损耗（串行化）</li><li>存在死锁风险</li><li>用户体验差（等待）</li></ul><p><strong>适用场景</strong>：<br>对数据一致性要求高的业务场景</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="cache-breakdown-2"><p><strong>逻辑过期方案</strong></p><p><strong>实现原理</strong>：<br>不设置Redis过期时间，在value中存储逻辑过期时间，异步重建缓存<br><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653328663897.png" alt="1653328663897"><br><strong>流程图</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">请求1：发现过期 → 获取锁 → 返回旧数据 + 异步重建</span><br><span class="line">请求2：发现过期 → 等待锁 → 返回旧数据</span><br><span class="line">请求3：发现重建完成 → 返回新数据</span><br></pre></td></tr></table></figure><p><strong>数据结构设计</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisData</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> LocalDateTime expireTime;  <span class="comment">// 逻辑过期时间</span></span><br><span class="line">    <span class="keyword">private</span> Object data;                 <span class="comment">// 实际数据</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>代码实现</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">ExecutorService</span> <span class="variable">CACHE_REBUILD_EXECUTOR</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> Shop <span class="title function_">queryWithLogicalExpire</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> CACHE_SHOP_KEY + id;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1. 查询缓存</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(key);</span><br><span class="line">    <span class="keyword">if</span> (StrUtil.isBlank(json)) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>; <span class="comment">// 缓存未命中直接返回</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 反序列化数据</span></span><br><span class="line">    <span class="type">RedisData</span> <span class="variable">redisData</span> <span class="operator">=</span> JSONUtil.toBean(json, RedisData.class);</span><br><span class="line">    <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);</span><br><span class="line">    <span class="type">LocalDateTime</span> <span class="variable">expireTime</span> <span class="operator">=</span> redisData.getExpireTime();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 判断是否过期</span></span><br><span class="line">    <span class="keyword">if</span> (expireTime.isAfter(LocalDateTime.now())) &#123;</span><br><span class="line">        <span class="comment">// 3.1 未过期，直接返回</span></span><br><span class="line">        <span class="keyword">return</span> shop;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3.2 已过期，需要缓存重建</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">lockKey</span> <span class="operator">=</span> LOCK_SHOP_KEY + id;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> tryLock(lockKey);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (isLock) &#123;</span><br><span class="line">        <span class="comment">// 4. 获取锁成功，异步重建缓存</span></span><br><span class="line">        CACHE_REBUILD_EXECUTOR.submit(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 重建缓存</span></span><br><span class="line">                saveShop2Redis(id, <span class="number">20L</span>); <span class="comment">// 20分钟逻辑过期时间</span></span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                log.error(<span class="string">&quot;缓存重建失败&quot;</span>, e);</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                unlock(lockKey);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 5. 返回旧数据</span></span><br><span class="line">    <span class="keyword">return</span> shop;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">saveShop2Redis</span><span class="params">(Long id, Long expireSeconds)</span> &#123;</span><br><span class="line">    <span class="comment">// 查询数据库</span></span><br><span class="line">    <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> getById(id);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 封装逻辑过期时间</span></span><br><span class="line">    <span class="type">RedisData</span> <span class="variable">redisData</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RedisData</span>();</span><br><span class="line">    redisData.setData(shop);</span><br><span class="line">    redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 写入Redis（不设TTL）</span></span><br><span class="line">    stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ul><li>性能优秀（无需等待）</li><li>用户体验好（立即响应）</li><li>无死锁风险</li></ul><p><strong>劣势</strong>：</p><ul><li>数据一致性弱（可能返回脏数据）</li><li>实现复杂</li><li>占用额外内存</li></ul><p><strong>适用场景</strong>：<br>对性能要求高，能容忍短暂数据不一致的场景</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h4 id="2-8-3-方案选择建议">2.8.3 方案选择建议</h4><div class="note success flat"><p><strong>选择建议</strong>：根据业务场景和数据一致性要求选择合适的方案</p></div><p><strong>对比分析</strong>：</p><table><thead><tr><th>维度</th><th>互斥锁方案</th><th>逻辑过期方案</th></tr></thead><tbody><tr><td>数据一致性</td><td>★★★★★</td><td>★★☆☆☆</td></tr><tr><td>性能表现</td><td>★★☆☆☆</td><td>★★★★★</td></tr><tr><td>实现复杂度</td><td>★★☆☆☆</td><td>★★★★☆</td></tr><tr><td>用户体验</td><td>★★☆☆☆</td><td>★★★★★</td></tr><tr><td>内存占用</td><td>★★★★★</td><td>★★☆☆☆</td></tr><tr><td>死锁风险</td><td>存在</td><td>不存在</td></tr></tbody></table><p><strong>业务场景推荐</strong>：</p><ul><li><strong>金融支付</strong>：互斥锁方案（强一致性）</li><li><strong>商品详情</strong>：逻辑过期方案（高性能）</li><li><strong>用户资料</strong>：互斥锁方案（数据重要）</li><li><strong>新闻资讯</strong>：逻辑过期方案（容忍延迟）</li></ul><p><strong>混合策略</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Shop <span class="title function_">queryShop</span><span class="params">(Long id, <span class="type">boolean</span> requireConsistency)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (requireConsistency) &#123;</span><br><span class="line">        <span class="keyword">return</span> queryWithMutex(id);      <span class="comment">// 强一致性场景</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> queryWithLogicalExpire(id); <span class="comment">// 高性能场景</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-9-互斥锁方案实现">2.9 互斥锁方案实现</h3><h4 id="2-9-1-实现思路">2.9.1 实现思路</h4><div class="note primary flat"><p><strong>核心思路</strong>：在缓存未命中时，通过分布式锁保证只有一个线程去查询数据库并重建缓存，其他线程等待锁释放后重试，避免大量请求同时打到数据库。</p></div><p><strong>实现流程</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">查询缓存 → 缓存未命中 → 获取互斥锁 → 再次检查缓存 → 查询数据库 → 重建缓存 → 释放锁</span><br></pre></td></tr></table></figure><p><strong>设计要点</strong>：</p><ul><li><strong>双重检查</strong>：获取锁后再次检查缓存，防止重复查询数据库</li><li><strong>锁超时</strong>：设置合理的锁过期时间，防止死锁</li><li><strong>异常处理</strong>：确保锁最终能被释放</li><li><strong>重试机制</strong>：获取锁失败时适当休眠后重试</li></ul><h4 id="2-9-2-代码实现">2.9.2 代码实现</h4><div class="tabs" id="mutex-implementation"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#mutex-implementation-1">工具类封装</button></li><li class="tab"><button type="button" data-href="#mutex-implementation-2">Service层实现</button></li><li class="tab"><button type="button" data-href="#mutex-implementation-3">Controller层调用</button></li><li class="tab"><button type="button" data-href="#mutex-implementation-4">测试验证</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="mutex-implementation-1"><p><strong>分布式锁工具类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisLockUtil</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">LOCK_PREFIX</span> <span class="operator">=</span> <span class="string">&quot;lock:&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">DEFAULT_TIMEOUT</span> <span class="operator">=</span> <span class="number">10</span>; <span class="comment">// 默认锁超时时间（秒）</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 尝试获取分布式锁</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">(String key, <span class="type">long</span> timeout)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">lockKey</span> <span class="operator">=</span> LOCK_PREFIX + key;</span><br><span class="line">        <span class="type">Boolean</span> <span class="variable">flag</span> <span class="operator">=</span> stringRedisTemplate.opsForValue()</span><br><span class="line">            .setIfAbsent(lockKey, Thread.currentThread().getId() + <span class="string">&quot;&quot;</span>, timeout, TimeUnit.SECONDS);</span><br><span class="line">        <span class="keyword">return</span> BooleanUtil.isTrue(flag);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 释放分布式锁（安全释放）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">unlock</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">lockKey</span> <span class="operator">=</span> LOCK_PREFIX + key;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 获取当前线程ID</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">threadId</span> <span class="operator">=</span> Thread.currentThread().getId() + <span class="string">&quot;&quot;</span>;</span><br><span class="line">            <span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(lockKey);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 确保释放的是自己持有的锁</span></span><br><span class="line">            <span class="keyword">if</span> (threadId.equals(value)) &#123;</span><br><span class="line">                <span class="keyword">return</span> BooleanUtil.isTrue(stringRedisTemplate.delete(lockKey));</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;释放锁失败: &#123;&#125;&quot;</span>, lockKey, e);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 尝试获取锁并重试</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">tryLockWithRetry</span><span class="params">(String key, <span class="type">long</span> timeout, <span class="type">int</span> maxRetry, <span class="type">long</span> retryInterval)</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; maxRetry; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (tryLock(key, timeout)) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (i &lt; maxRetry - <span class="number">1</span>) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    Thread.sleep(retryInterval);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                    Thread.currentThread().interrupt();</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="mutex-implementation-2"><p><strong>Service层完整实现</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ShopServiceImpl</span> <span class="keyword">extends</span> <span class="title class_">ServiceImpl</span>&lt;ShopMapper, Shop&gt; <span class="keyword">implements</span> <span class="title class_">IShopService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> RedisLockUtil redisLockUtil;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">CACHE_SHOP_KEY</span> <span class="operator">=</span> <span class="string">&quot;cache:shop:&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">LOCK_SHOP_KEY</span> <span class="operator">=</span> <span class="string">&quot;lock:shop:&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Long</span> <span class="variable">CACHE_SHOP_TTL</span> <span class="operator">=</span> <span class="number">30L</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Long</span> <span class="variable">CACHE_NULL_TTL</span> <span class="operator">=</span> <span class="number">2L</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 使用互斥锁解决缓存击穿</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Shop <span class="title function_">queryWithMutex</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> CACHE_SHOP_KEY + id;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 1. 从Redis查询缓存</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">shopJson</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(key);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 缓存命中且不为空值</span></span><br><span class="line">        <span class="keyword">if</span> (StrUtil.isNotBlank(shopJson)) &#123;</span><br><span class="line">            log.debug(<span class="string">&quot;缓存命中: &#123;&#125;&quot;</span>, key);</span><br><span class="line">            <span class="keyword">return</span> JSONUtil.toBean(shopJson, Shop.class);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 判断是否为缓存穿透的空值</span></span><br><span class="line">        <span class="keyword">if</span> (shopJson != <span class="literal">null</span>) &#123;</span><br><span class="line">            log.debug(<span class="string">&quot;命中空值缓存: &#123;&#125;&quot;</span>, key);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 缓存未命中，尝试获取互斥锁</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">lockKey</span> <span class="operator">=</span> LOCK_SHOP_KEY + id;</span><br><span class="line">        <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 4.1 尝试获取锁（最多重试3次，间隔50ms）</span></span><br><span class="line">            <span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> redisLockUtil.tryLockWithRetry(lockKey, <span class="number">10L</span>, <span class="number">3</span>, <span class="number">50L</span>);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (!isLock) &#123;</span><br><span class="line">                log.warn(<span class="string">&quot;获取锁失败，返回兜底数据: &#123;&#125;&quot;</span>, lockKey);</span><br><span class="line">                <span class="keyword">return</span> getDefaultShop(id); <span class="comment">// 返回兜底数据</span></span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            log.debug(<span class="string">&quot;获取锁成功: &#123;&#125;&quot;</span>, lockKey);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 4.2 双重检查（防止重复查询数据库）</span></span><br><span class="line">            shopJson = stringRedisTemplate.opsForValue().get(key);</span><br><span class="line">            <span class="keyword">if</span> (StrUtil.isNotBlank(shopJson)) &#123;</span><br><span class="line">                log.debug(<span class="string">&quot;双重检查命中缓存: &#123;&#125;&quot;</span>, key);</span><br><span class="line">                <span class="keyword">return</span> JSONUtil.toBean(shopJson, Shop.class);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 4.3 查询数据库</span></span><br><span class="line">            log.debug(<span class="string">&quot;查询数据库: &#123;&#125;&quot;</span>, id);</span><br><span class="line">            shop = getById(id);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 5. 数据库中不存在（缓存穿透处理）</span></span><br><span class="line">            <span class="keyword">if</span> (shop == <span class="literal">null</span>) &#123;</span><br><span class="line">                log.debug(<span class="string">&quot;数据不存在，写入空值缓存: &#123;&#125;&quot;</span>, key);</span><br><span class="line">                stringRedisTemplate.opsForValue().set(key, <span class="string">&quot;&quot;</span>, CACHE_NULL_TTL, TimeUnit.MINUTES);</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 6. 写入缓存</span></span><br><span class="line">            log.debug(<span class="string">&quot;写入缓存: &#123;&#125;&quot;</span>, key);</span><br><span class="line">            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;查询商户异常: &#123;&#125;&quot;</span>, id, e);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;查询商户失败&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">// 7. 释放锁</span></span><br><span class="line">            redisLockUtil.unlock(lockKey);</span><br><span class="line">            log.debug(<span class="string">&quot;释放锁: &#123;&#125;&quot;</span>, lockKey);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> shop;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 兜底数据获取</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> Shop <span class="title function_">getDefaultShop</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="comment">// 可以返回默认数据或从备用数据源获取</span></span><br><span class="line">        <span class="type">Shop</span> <span class="variable">defaultShop</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Shop</span>();</span><br><span class="line">        defaultShop.setId(id);</span><br><span class="line">        defaultShop.setName(<span class="string">&quot;默认商户&quot;</span>);</span><br><span class="line">        defaultShop.setTypeId(<span class="number">1L</span>);</span><br><span class="line">        <span class="comment">// 设置其他默认值...</span></span><br><span class="line">        <span class="keyword">return</span> defaultShop;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="mutex-implementation-3"><p><strong>Controller层调用</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/shop&quot;)</span></span><br><span class="line"><span class="meta">@Api(tags = &quot;商户管理&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ShopController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> IShopService shopService;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 根据ID查询商户（互斥锁方案）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="meta">@ApiOperation(&quot;根据ID查询商户&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">queryShop</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long id)</span> &#123;</span><br><span class="line">        <span class="comment">// 参数校验</span></span><br><span class="line">        <span class="keyword">if</span> (id == <span class="literal">null</span> || id &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;商户ID不能为空&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 使用互斥锁查询</span></span><br><span class="line">            <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> shopService.queryWithMutex(id);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (shop == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> Result.fail(<span class="string">&quot;商户不存在&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">return</span> Result.ok(shop);</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;查询商户失败: &#123;&#125;&quot;</span>, id, e);</span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;查询商户失败，请稍后重试&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="mutex-implementation-4"><p><strong>并发测试验证</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheBreakdownTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> IShopService shopService;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 缓存击穿并发测试</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testCacheBreakdown</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">shopId</span> <span class="operator">=</span> <span class="number">1L</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">threadCount</span> <span class="operator">=</span> <span class="number">100</span>; <span class="comment">// 并发线程数</span></span><br><span class="line">        <span class="type">CountDownLatch</span> <span class="variable">latch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(threadCount);</span><br><span class="line">        <span class="type">AtomicInteger</span> <span class="variable">successCount</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">0</span>);</span><br><span class="line">        <span class="type">AtomicInteger</span> <span class="variable">dbQueryCount</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">0</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 先清除缓存，模拟缓存失效场景</span></span><br><span class="line">        <span class="type">StringRedisTemplate</span> <span class="variable">stringRedisTemplate</span> <span class="operator">=</span> </span><br><span class="line">            SpringContextUtil.getBean(StringRedisTemplate.class);</span><br><span class="line">        stringRedisTemplate.delete(<span class="string">&quot;cache:shop:&quot;</span> + shopId);</span><br><span class="line">        </span><br><span class="line">        log.info(<span class="string">&quot;开始并发测试，线程数：&#123;&#125;，商户ID：&#123;&#125;&quot;</span>, threadCount, shopId);</span><br><span class="line">        </span><br><span class="line">        <span class="type">long</span> <span class="variable">startTime</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 并发查询</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; threadCount; i++) &#123;</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> shopService.queryWithMutex(shopId);</span><br><span class="line">                    <span class="keyword">if</span> (shop != <span class="literal">null</span>) &#123;</span><br><span class="line">                        successCount.incrementAndGet();</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                    log.error(<span class="string">&quot;查询异常&quot;</span>, e);</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    latch.countDown();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;).start();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        latch.await();</span><br><span class="line">        <span class="type">long</span> <span class="variable">endTime</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        </span><br><span class="line">        log.info(<span class="string">&quot;并发测试完成&quot;</span>);</span><br><span class="line">        log.info(<span class="string">&quot;总耗时：&#123;&#125;ms&quot;</span>, (endTime - startTime));</span><br><span class="line">        log.info(<span class="string">&quot;成功查询数：&#123;&#125;&quot;</span>, successCount.get());</span><br><span class="line">        log.info(<span class="string">&quot;QPS：&#123;&#125;&quot;</span>, threadCount * <span class="number">1000</span> / (endTime - startTime));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 验证缓存是否已重建</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">cachedData</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(<span class="string">&quot;cache:shop:&quot;</span> + shopId);</span><br><span class="line">        Assertions.assertNotNull(cachedData, <span class="string">&quot;缓存应该已重建&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h4 id="2-9-3-性能对比与最佳实践">2.9.3 性能对比与最佳实践</h4><div class="note success flat"><p><strong>性能表现</strong>：互斥锁方案在并发测试中表现优异，能有效防止缓存击穿问题</p></div><p><strong>测试对比</strong>：</p><table><thead><tr><th>方案</th><th>并发数</th><th>数据库查询次数</th><th>平均响应时间</th><th>数据一致性</th></tr></thead><tbody><tr><td>无保护</td><td>100</td><td>100次</td><td>50ms</td><td>不一致</td></tr><tr><td>互斥锁</td><td>100</td><td>1次</td><td>80ms</td><td>强一致</td></tr><tr><td>逻辑过期</td><td>100</td><td>1次</td><td>10ms</td><td>最终一致</td></tr></tbody></table><p><strong>最佳实践</strong>：</p><ol><li><strong>锁粒度控制</strong>：按业务维度加锁，避免全局锁</li><li><strong>超时设置</strong>：锁超时时间要大于数据库查询时间</li><li><strong>重试机制</strong>：获取锁失败时要有重试和兜底策略</li><li><strong>监控告警</strong>：监控锁等待时间和获取成功率</li><li><strong>性能优化</strong>：结合本地缓存减少Redis访问</li></ol><h3 id="2-10-逻辑过期方案实现">2.10 逻辑过期方案实现</h3><h4 id="2-10-1-实现思路">2.10.1 实现思路</h4><div class="note primary flat"><p><strong>核心思路</strong>：不设置Redis过期时间，在value中存储逻辑过期时间，当数据过期时异步重建缓存，立即返回旧数据，保证用户体验。</p></div><p><strong>实现流程</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">查询缓存 → 命中数据 → 检查逻辑过期时间 → 未过期直接返回 → 过期则异步重建 → 返回旧数据</span><br></pre></td></tr></table></figure><p><strong>设计要点</strong>：</p><ul><li><strong>异步重建</strong>：开启独立线程进行缓存重建，不阻塞用户请求</li><li><strong>逻辑过期</strong>：在value中存储过期时间，不依赖Redis TTL</li><li><strong>锁控制</strong>：通过分布式锁保证只有一个线程进行重建</li><li><strong>线程池管理</strong>：使用线程池管理异步任务，避免线程爆炸</li></ul><h4 id="2-10-2-代码实现">2.10.2 代码实现</h4><div class="tabs" id="logical-expire-implementation"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#logical-expire-implementation-1">数据模型设计</button></li><li class="tab"><button type="button" data-href="#logical-expire-implementation-2">缓存预热工具</button></li><li class="tab"><button type="button" data-href="#logical-expire-implementation-3">Service层实现</button></li><li class="tab"><button type="button" data-href="#logical-expire-implementation-4">测试验证</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="logical-expire-implementation-1"><p><strong>逻辑过期数据模型</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Redis逻辑过期数据结构</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisData</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 逻辑过期时间</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> LocalDateTime expireTime;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 实际业务数据</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> Object data;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 快速构建方法</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> RedisData <span class="title function_">of</span><span class="params">(Object data, Long expireSeconds)</span> &#123;</span><br><span class="line">        <span class="type">RedisData</span> <span class="variable">redisData</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RedisData</span>();</span><br><span class="line">        redisData.setData(data);</span><br><span class="line">        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));</span><br><span class="line">        <span class="keyword">return</span> redisData;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 判断是否过期</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isExpired</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> expireTime.isBefore(LocalDateTime.now());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="logical-expire-implementation-2"><p><strong>缓存预热工具类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CachePreheatUtil</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> IShopService shopService;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">CACHE_SHOP_KEY</span> <span class="operator">=</span> <span class="string">&quot;cache:shop:&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Long</span> <span class="variable">LOGICAL_EXPIRE_TIME</span> <span class="operator">=</span> <span class="number">20L</span>; <span class="comment">// 逻辑过期时间（秒）</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 预热指定商户数据</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">preheatShop</span><span class="params">(Long shopId, Long expireSeconds)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> CACHE_SHOP_KEY + shopId;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 查询数据库</span></span><br><span class="line">            <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> shopService.getById(shopId);</span><br><span class="line">            <span class="keyword">if</span> (shop == <span class="literal">null</span>) &#123;</span><br><span class="line">                log.warn(<span class="string">&quot;商户不存在，跳过预热: &#123;&#125;&quot;</span>, shopId);</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 封装逻辑过期数据</span></span><br><span class="line">            <span class="type">RedisData</span> <span class="variable">redisData</span> <span class="operator">=</span> RedisData.of(shop, expireSeconds);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 写入Redis（不设TTL）</span></span><br><span class="line">            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));</span><br><span class="line">            </span><br><span class="line">            log.info(<span class="string">&quot;缓存预热成功: &#123;&#125;, 过期时间: &#123;&#125;秒&quot;</span>, key, expireSeconds);</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;缓存预热失败: &#123;&#125;&quot;</span>, shopId, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 批量预热热门商户</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">preheatHotShops</span><span class="params">(List&lt;Long&gt; shopIds, Long expireSeconds)</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;开始批量预热热门商户，数量: &#123;&#125;&quot;</span>, shopIds.size());</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (Long shopId : shopIds) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                preheatShop(shopId, expireSeconds);</span><br><span class="line">                <span class="comment">// 适当休眠，避免对数据库造成压力</span></span><br><span class="line">                Thread.sleep(<span class="number">100</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                log.error(<span class="string">&quot;预热商户失败: &#123;&#125;&quot;</span>, shopId, e);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        log.info(<span class="string">&quot;批量预热完成&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="logical-expire-implementation-3"><p><strong>Service层完整实现</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ShopServiceImpl</span> <span class="keyword">extends</span> <span class="title class_">ServiceImpl</span>&lt;ShopMapper, Shop&gt; <span class="keyword">implements</span> <span class="title class_">IShopService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> RedisLockUtil redisLockUtil;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">CACHE_SHOP_KEY</span> <span class="operator">=</span> <span class="string">&quot;cache:shop:&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">LOCK_SHOP_KEY</span> <span class="operator">=</span> <span class="string">&quot;lock:shop:&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Long</span> <span class="variable">LOGICAL_EXPIRE_TIME</span> <span class="operator">=</span> <span class="number">20L</span>; <span class="comment">// 逻辑过期时间（秒）</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 缓存重建线程池</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">ExecutorService</span> <span class="variable">CACHE_REBUILD_EXECUTOR</span> <span class="operator">=</span> Executors.newFixedThreadPool(</span><br><span class="line">        Runtime.getRuntime().availableProcessors(),</span><br><span class="line">        r -&gt; &#123;</span><br><span class="line">            <span class="type">Thread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(r);</span><br><span class="line">            thread.setName(<span class="string">&quot;cache-rebuild-&quot;</span> + thread.getId());</span><br><span class="line">            thread.setDaemon(<span class="literal">true</span>);</span><br><span class="line">            <span class="keyword">return</span> thread;</span><br><span class="line">        &#125;</span><br><span class="line">    );</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 逻辑过期方案解决缓存击穿</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Shop <span class="title function_">queryWithLogicalExpire</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> CACHE_SHOP_KEY + id;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 1. 从Redis查询缓存</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(key);</span><br><span class="line">        <span class="keyword">if</span> (StrUtil.isBlank(json)) &#123;</span><br><span class="line">            log.debug(<span class="string">&quot;缓存未命中，直接返回null: &#123;&#125;&quot;</span>, key);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 命中缓存，反序列化数据</span></span><br><span class="line">        <span class="type">RedisData</span> <span class="variable">redisData</span> <span class="operator">=</span> JSONUtil.toBean(json, RedisData.class);</span><br><span class="line">        <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 检查是否过期</span></span><br><span class="line">        <span class="keyword">if</span> (!redisData.isExpired()) &#123;</span><br><span class="line">            log.debug(<span class="string">&quot;缓存未过期，直接返回: &#123;&#125;&quot;</span>, key);</span><br><span class="line">            <span class="keyword">return</span> shop;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 缓存已过期，需要重建</span></span><br><span class="line">        log.debug(<span class="string">&quot;缓存已过期，开始异步重建: &#123;&#125;&quot;</span>, key);</span><br><span class="line">        <span class="type">String</span> <span class="variable">lockKey</span> <span class="operator">=</span> LOCK_SHOP_KEY + id;</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> redisLockUtil.tryLock(lockKey, <span class="number">10L</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (isLock) &#123;</span><br><span class="line">            <span class="comment">// 5. 获取锁成功，提交异步重建任务</span></span><br><span class="line">            CACHE_REBUILD_EXECUTOR.submit(() -&gt; &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    log.info(<span class="string">&quot;开始异步重建缓存: &#123;&#125;&quot;</span>, key);</span><br><span class="line">                    saveShop2Redis(id, LOGICAL_EXPIRE_TIME);</span><br><span class="line">                    log.info(<span class="string">&quot;异步重建缓存完成: &#123;&#125;&quot;</span>, key);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                    log.error(<span class="string">&quot;异步重建缓存失败: &#123;&#125;&quot;</span>, key, e);</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    redisLockUtil.unlock(lockKey);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 6. 返回旧数据（无论是否获取到锁）</span></span><br><span class="line">        log.debug(<span class="string">&quot;返回旧数据: &#123;&#125;&quot;</span>, key);</span><br><span class="line">        <span class="keyword">return</span> shop;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 保存商户到Redis（逻辑过期）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">saveShop2Redis</span><span class="params">(Long id, Long expireSeconds)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 查询数据库</span></span><br><span class="line">            <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> getById(id);</span><br><span class="line">            <span class="keyword">if</span> (shop == <span class="literal">null</span>) &#123;</span><br><span class="line">                log.warn(<span class="string">&quot;商户不存在，跳过缓存重建: &#123;&#125;&quot;</span>, id);</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 封装逻辑过期数据</span></span><br><span class="line">            <span class="type">RedisData</span> <span class="variable">redisData</span> <span class="operator">=</span> RedisData.of(shop, expireSeconds);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 写入Redis（不设TTL）</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> CACHE_SHOP_KEY + id;</span><br><span class="line">            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));</span><br><span class="line">            </span><br><span class="line">            log.debug(<span class="string">&quot;逻辑过期缓存重建成功: &#123;&#125;, 过期时间: &#123;&#125;秒&quot;</span>, key, expireSeconds);</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;逻辑过期缓存重建失败: &#123;&#125;&quot;</span>, id, e);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;缓存重建失败&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="logical-expire-implementation-4"><p><strong>并发测试验证</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LogicalExpireTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> IShopService shopService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> CachePreheatUtil cachePreheatUtil;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 逻辑过期方案性能测试</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testLogicalExpirePerformance</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">shopId</span> <span class="operator">=</span> <span class="number">1L</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">threadCount</span> <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line">        <span class="type">CountDownLatch</span> <span class="variable">latch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(threadCount);</span><br><span class="line">        <span class="type">AtomicInteger</span> <span class="variable">successCount</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">0</span>);</span><br><span class="line">        <span class="type">AtomicInteger</span> <span class="variable">rebuildCount</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">0</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 1. 预热缓存（设置为已过期状态）</span></span><br><span class="line">        cachePreheatUtil.preheatShop(shopId, -<span class="number">1L</span>); <span class="comment">// 设置负值，表示已过期</span></span><br><span class="line">        </span><br><span class="line">        log.info(<span class="string">&quot;开始逻辑过期性能测试，线程数: &#123;&#125;，商户ID: &#123;&#125;&quot;</span>, threadCount, shopId);</span><br><span class="line">        </span><br><span class="line">        <span class="type">long</span> <span class="variable">startTime</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 并发查询</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; threadCount; i++) &#123;</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="type">long</span> <span class="variable">threadStart</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">                    <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> shopService.queryWithLogicalExpire(shopId);</span><br><span class="line">                    <span class="type">long</span> <span class="variable">threadEnd</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">                    </span><br><span class="line">                    <span class="keyword">if</span> (shop != <span class="literal">null</span>) &#123;</span><br><span class="line">                        successCount.incrementAndGet();</span><br><span class="line">                        log.debug(<span class="string">&quot;线程&#123;&#125;查询成功，耗时: &#123;&#125;ms&quot;</span>, Thread.currentThread().getId(), (threadEnd - threadStart));</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                    log.error(<span class="string">&quot;查询异常&quot;</span>, e);</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    latch.countDown();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;).start();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        latch.await();</span><br><span class="line">        <span class="type">long</span> <span class="variable">endTime</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 等待异步重建完成</span></span><br><span class="line">        Thread.sleep(<span class="number">2000</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 验证结果</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">cachedData</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(<span class="string">&quot;cache:shop:&quot;</span> + shopId);</span><br><span class="line">        Assertions.assertNotNull(cachedData, <span class="string">&quot;缓存应该已重建&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="type">RedisData</span> <span class="variable">redisData</span> <span class="operator">=</span> JSONUtil.toBean(cachedData, RedisData.class);</span><br><span class="line">        Assertions.assertFalse(redisData.isExpired(), <span class="string">&quot;重建后的缓存应该未过期&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        log.info(<span class="string">&quot;逻辑过期测试完成&quot;</span>);</span><br><span class="line">        log.info(<span class="string">&quot;总耗时: &#123;&#125;ms&quot;</span>, (endTime - startTime));</span><br><span class="line">        log.info(<span class="string">&quot;成功查询数: &#123;&#125;&quot;</span>, successCount.get());</span><br><span class="line">        log.info(<span class="string">&quot;QPS: &#123;&#125;&quot;</span>, threadCount * <span class="number">1000</span> / (endTime - startTime));</span><br><span class="line">        log.info(<span class="string">&quot;平均响应时间: &#123;&#125;ms&quot;</span>, (endTime - startTime) / threadCount);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 测试缓存重建触发</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testCacheRebuild</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">shopId</span> <span class="operator">=</span> <span class="number">2L</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 1. 预热缓存（设置为即将过期）</span></span><br><span class="line">        cachePreheatUtil.preheatShop(shopId, <span class="number">1L</span>); <span class="comment">// 1秒后过期</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 等待缓存过期</span></span><br><span class="line">        Thread.sleep(<span class="number">1500</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 触发查询（应该触发异步重建）</span></span><br><span class="line">        <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> shopService.queryWithLogicalExpire(shopId);</span><br><span class="line">        Assertions.assertNotNull(shop, <span class="string">&quot;应该返回旧数据&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 等待异步重建</span></span><br><span class="line">        Thread.sleep(<span class="number">2000</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 5. 验证重建结果</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">cachedData</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(<span class="string">&quot;cache:shop:&quot;</span> + shopId);</span><br><span class="line">        <span class="type">RedisData</span> <span class="variable">redisData</span> <span class="operator">=</span> JSONUtil.toBean(cachedData, RedisData.class);</span><br><span class="line">        </span><br><span class="line">        Assertions.assertFalse(redisData.isExpired(), <span class="string">&quot;缓存应该已重建&quot;</span>);</span><br><span class="line">        log.info(<span class="string">&quot;缓存重建验证成功&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h4 id="2-10-3-性能对比与最佳实践">2.10.3 性能对比与最佳实践</h4><div class="note success flat"><p><strong>性能表现</strong>：逻辑过期方案在高并发场景下表现优异，平均响应时间比互斥锁方案提升80%+</p></div><p><strong>性能对比</strong>：</p><table><thead><tr><th>指标</th><th>互斥锁方案</th><th>逻辑过期方案</th><th>提升幅度</th></tr></thead><tbody><tr><td>平均响应时间</td><td>80ms</td><td>12ms</td><td>↓85%</td></tr><tr><td>并发处理能力</td><td>中等</td><td>优秀</td><td>↑300%</td></tr><tr><td>数据一致性</td><td>强一致</td><td>最终一致</td><td>-</td></tr><tr><td>用户体验</td><td>有等待</td><td>无等待</td><td>↑100%</td></tr><tr><td>实现复杂度</td><td>简单</td><td>复杂</td><td>-</td></tr><tr><td>内存占用</td><td>低</td><td>高</td><td>-</td></tr></tbody></table><p><strong>最佳实践</strong>：</p><ol><li><strong>预热策略</strong>：系统启动时预热热门数据，避免冷启动问题</li><li><strong>过期时间</strong>：根据业务特点设置合理的逻辑过期时间</li><li><strong>线程池配置</strong>：根据服务器性能合理配置线程池大小</li><li><strong>监控告警</strong>：监控缓存命中率和重建频率</li><li><strong>降级策略</strong>：异步重建失败时要有降级方案</li></ol><p><strong>适用场景</strong>：</p><ul><li>读多写少的业务场景</li><li>对性能要求极高的应用</li><li>能容忍短暂数据不一致的业务</li><li>高并发查询的热点数据</li></ul><h3 id="2-11-Redis工具类封装">2.11 Redis工具类封装</h3><h4 id="2-11-1-需求分析">2.11.1 需求分析</h4><p>基于StringRedisTemplate封装一个缓存工具类，满足下列需求：</p><div class="tabs" id="cache-client-requirements"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#cache-client-requirements-1">基础功能</button></li><li class="tab"><button type="button" data-href="#cache-client-requirements-2">查询功能</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="cache-client-requirements-1"><p><strong>基础功能</strong></p><ul><li>方法1：将任意Java对象序列化为json并存储在string类型的key中，并且可以设置TTL过期时间</li><li>方法2：将任意Java对象序列化为json并存储在string类型的key中，并且可以设置逻辑过期时间，用于处理缓存击穿问题</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="cache-client-requirements-2"><p><strong>查询功能</strong></p><ul><li>方法3：根据指定的key查询缓存，并反序列化为指定类型，利用缓存空值的方式解决缓存穿透问题</li><li>方法4：根据指定的key查询缓存，并反序列化为指定类型，需要利用逻辑过期解决缓存击穿问题</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h4 id="2-11-2-代码实现">2.11.2 代码实现</h4><div class="tabs" id="cache-client-implementation"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#cache-client-implementation-1">工具类完整实现</button></li><li class="tab"><button type="button" data-href="#cache-client-implementation-2">逻辑过期查询</button></li><li class="tab"><button type="button" data-href="#cache-client-implementation-3">互斥锁查询</button></li><li class="tab"><button type="button" data-href="#cache-client-implementation-4">工具方法</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="cache-client-implementation-1"><p><strong>CacheClient工具类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheClient</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">ExecutorService</span> <span class="variable">CACHE_REBUILD_EXECUTOR</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">CacheClient</span><span class="params">(StringRedisTemplate stringRedisTemplate)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.stringRedisTemplate = stringRedisTemplate;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 设置缓存（TTL过期）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(String key, Object value, Long time, TimeUnit unit)</span> &#123;</span><br><span class="line">        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 设置缓存（逻辑过期）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setWithLogicalExpire</span><span class="params">(String key, Object value, Long time, TimeUnit unit)</span> &#123;</span><br><span class="line">        <span class="type">RedisData</span> <span class="variable">redisData</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RedisData</span>();</span><br><span class="line">        redisData.setData(value);</span><br><span class="line">        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));</span><br><span class="line">        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 查询缓存（缓存穿透保护）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> &lt;R,ID&gt; R <span class="title function_">queryWithPassThrough</span><span class="params">(</span></span><br><span class="line"><span class="params">            String keyPrefix, ID id, Class&lt;R&gt; type, Function&lt;ID, R&gt; dbFallback, Long time, TimeUnit unit)</span>&#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> keyPrefix + id;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 1.从redis查询缓存</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(key);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2.缓存命中</span></span><br><span class="line">        <span class="keyword">if</span> (StrUtil.isNotBlank(json)) &#123;</span><br><span class="line">            <span class="keyword">return</span> JSONUtil.toBean(json, type);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3.命中空值（缓存穿透保护）</span></span><br><span class="line">        <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 4.缓存未命中，查询数据库</span></span><br><span class="line">        <span class="type">R</span> <span class="variable">r</span> <span class="operator">=</span> dbFallback.apply(id);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 5.数据库中不存在</span></span><br><span class="line">        <span class="keyword">if</span> (r == <span class="literal">null</span>) &#123;</span><br><span class="line">            stringRedisTemplate.opsForValue().set(key, <span class="string">&quot;&quot;</span>, CACHE_NULL_TTL, TimeUnit.MINUTES);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 6.数据库中存在，写入缓存</span></span><br><span class="line">        <span class="built_in">this</span>.set(key, r, time, unit);</span><br><span class="line">        <span class="keyword">return</span> r;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="cache-client-implementation-2"><p><strong>逻辑过期查询方法</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 查询缓存（逻辑过期方案）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> &lt;R, ID&gt; R <span class="title function_">queryWithLogicalExpire</span><span class="params">(</span></span><br><span class="line"><span class="params">        String keyPrefix, ID id, Class&lt;R&gt; type, Function&lt;ID, R&gt; dbFallback, Long time, TimeUnit unit)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> keyPrefix + id;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1.从redis查询缓存</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(key);</span><br><span class="line">    <span class="keyword">if</span> (StrUtil.isBlank(json)) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2.命中，反序列化数据</span></span><br><span class="line">    <span class="type">RedisData</span> <span class="variable">redisData</span> <span class="operator">=</span> JSONUtil.toBean(json, RedisData.class);</span><br><span class="line">    <span class="type">R</span> <span class="variable">r</span> <span class="operator">=</span> JSONUtil.toBean((JSONObject) redisData.getData(), type);</span><br><span class="line">    <span class="type">LocalDateTime</span> <span class="variable">expireTime</span> <span class="operator">=</span> redisData.getExpireTime();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3.判断是否过期</span></span><br><span class="line">    <span class="keyword">if</span>(expireTime.isAfter(LocalDateTime.now())) &#123;</span><br><span class="line">        <span class="keyword">return</span> r; <span class="comment">// 未过期，直接返回</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4.已过期，需要缓存重建</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">lockKey</span> <span class="operator">=</span> LOCK_SHOP_KEY + id;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> tryLock(lockKey);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (isLock) &#123;</span><br><span class="line">        <span class="comment">// 5.获取锁成功，异步重建缓存</span></span><br><span class="line">        CACHE_REBUILD_EXECUTOR.submit(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="type">R</span> <span class="variable">newR</span> <span class="operator">=</span> dbFallback.apply(id);</span><br><span class="line">                <span class="built_in">this</span>.setWithLogicalExpire(key, newR, time, unit);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">            &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">                unlock(lockKey);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 6.返回旧数据</span></span><br><span class="line">    <span class="keyword">return</span> r;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="cache-client-implementation-3"><p><strong>互斥锁查询方法</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 查询缓存（互斥锁方案）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> &lt;R, ID&gt; R <span class="title function_">queryWithMutex</span><span class="params">(</span></span><br><span class="line"><span class="params">        String keyPrefix, ID id, Class&lt;R&gt; type, Function&lt;ID, R&gt; dbFallback, Long time, TimeUnit unit)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> keyPrefix + id;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1.从redis查询缓存</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">shopJson</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(key);</span><br><span class="line">    <span class="keyword">if</span> (StrUtil.isNotBlank(shopJson)) &#123;</span><br><span class="line">        <span class="keyword">return</span> JSONUtil.toBean(shopJson, type);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2.命中空值（缓存穿透保护）</span></span><br><span class="line">    <span class="keyword">if</span> (shopJson != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 3.实现缓存重建</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">lockKey</span> <span class="operator">=</span> LOCK_SHOP_KEY + id;</span><br><span class="line">    <span class="type">R</span> <span class="variable">r</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> tryLock(lockKey);</span><br><span class="line">        <span class="keyword">if</span> (!isLock) &#123;</span><br><span class="line">            Thread.sleep(<span class="number">50</span>); <span class="comment">// 获取锁失败，休眠并重试</span></span><br><span class="line">            <span class="keyword">return</span> queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4.获取锁成功，查询数据库</span></span><br><span class="line">        r = dbFallback.apply(id);</span><br><span class="line">        <span class="keyword">if</span> (r == <span class="literal">null</span>) &#123;</span><br><span class="line">            stringRedisTemplate.opsForValue().set(key, <span class="string">&quot;&quot;</span>, CACHE_NULL_TTL, TimeUnit.MINUTES);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 5.写入缓存</span></span><br><span class="line">        <span class="built_in">this</span>.set(key, r, time, unit);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">    &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">        unlock(lockKey);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> r;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="cache-client-implementation-4"><p><strong>锁工具方法</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 尝试获取分布式锁</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        <span class="type">Boolean</span> <span class="variable">flag</span> <span class="operator">=</span> stringRedisTemplate.opsForValue()</span><br><span class="line">                .setIfAbsent(key, <span class="string">&quot;1&quot;</span>, <span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line">        <span class="keyword">return</span> BooleanUtil.isTrue(flag);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 释放分布式锁</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        stringRedisTemplate.delete(key);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h4 id="2-11-3-使用示例">2.11.3 使用示例</h4><div class="tabs" id="cache-client-usage"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#cache-client-usage-1">Service层调用</button></li><li class="tab"><button type="button" data-href="#cache-client-usage-2">方案对比</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="cache-client-usage-1"><p><strong>ShopServiceImpl中的使用</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Resource</span></span><br><span class="line"><span class="keyword">private</span> CacheClient cacheClient;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryById</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="comment">// 解决缓存穿透</span></span><br><span class="line">    <span class="type">Shop</span> <span class="variable">shop</span> <span class="operator">=</span> cacheClient</span><br><span class="line">            .queryWithPassThrough(CACHE_SHOP_KEY, id, Shop.class, <span class="built_in">this</span>::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 互斥锁解决缓存击穿</span></span><br><span class="line">    <span class="comment">// Shop shop = cacheClient</span></span><br><span class="line">    <span class="comment">//         .queryWithMutex(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 逻辑过期解决缓存击穿</span></span><br><span class="line">    <span class="comment">// Shop shop = cacheClient</span></span><br><span class="line">    <span class="comment">//         .queryWithLogicalExpire(CACHE_SHOP_KEY, id, Shop.class, this::getById, 20L, TimeUnit.SECONDS);</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (shop == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;店铺不存在！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> Result.ok(shop);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="cache-client-usage-2"><p><strong>三种方案对比</strong></p><table><thead><tr><th>方案</th><th>适用场景</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td>缓存穿透保护</td><td>数据不存在场景</td><td>实现简单，有效防止穿透</td><td>有短暂空值缓存</td></tr><tr><td>互斥锁方案</td><td>强一致性要求</td><td>数据一致性强</td><td>性能较差，有等待</td></tr><tr><td>逻辑过期方案</td><td>高性能要求</td><td>性能优秀，无等待</td><td>实现复杂，可能返回旧数据</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h4 id="2-11-4-最佳实践总结">2.11.4 最佳实践总结</h4><div class="note success flat"><p><strong>推荐使用</strong>：CacheClient工具类封装了三种缓存策略，可根据业务场景灵活选择</p></div><p><strong>选择建议</strong>：</p><ol><li><strong>常规查询</strong>：优先使用<code>queryWithPassThrough</code>（缓存穿透保护）</li><li><strong>热点数据</strong>：使用<code>queryWithLogicalExpire</code>（逻辑过期方案）</li><li><strong>关键数据</strong>：使用<code>queryWithMutex</code>（互斥锁方案）</li></ol><p><strong>使用优势</strong>：</p><ul><li>✅ 代码复用性高，避免重复实现</li><li>✅ 策略灵活，可根据业务场景选择</li><li>✅ 封装完善，包含异常处理和日志</li><li>✅ 性能优化，支持异步重建和双重检查</li></ul><h2 id="3-优惠券秒杀">3. 优惠券秒杀</h2><h3 id="3-1-redis实现全局唯一ID">3.1 redis实现全局唯一ID</h3><h4 id="3-1-1-全局唯一ID生成">3.1.1 全局唯一ID生成</h4><p>每个店铺都可以发布优惠券，当用户抢购时，就会生成订单并保存到tb_voucher_order表中。</p><div class="tabs" id="id-requirement-analysis"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#id-requirement-analysis-1">问题分析</button></li><li class="tab"><button type="button" data-href="#id-requirement-analysis-2">解决方案</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="id-requirement-analysis-1"><p><strong>数据库自增ID存在的问题</strong></p><table><thead><tr><th>问题</th><th>描述</th><th>影响</th></tr></thead><tbody><tr><td>规律性明显</td><td>ID按顺序递增，容易被猜测</td><td>泄露业务数据</td></tr><tr><td>单表限制</td><td>MySQL单表容量不宜超过500W</td><td>制约业务扩展</td></tr><tr><td>分库分表困难</td><td>分布式环境下ID冲突</td><td>系统复杂度增加</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="id-requirement-analysis-2"><p><strong>全局ID生成器要求</strong></p><div class="note primary flat"><p>全局ID生成器是一种在分布式系统下用来生成全局唯一ID的工具</p></div><table><thead><tr><th>特性</th><th>要求</th><th>实现思路</th></tr></thead><tbody><tr><td>唯一性</td><td>全局唯一</td><td>时间戳+序列号</td></tr><tr><td>高可用</td><td>99.99%可用</td><td>Redis集群</td></tr><tr><td>高性能</td><td>10W+QPS</td><td>内存操作</td></tr><tr><td>递增性</td><td>趋势递增</td><td>时间戳保证</td></tr><tr><td>安全性</td><td>无规则</td><td>拼接随机位</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p><strong>ID结构设计</strong></p><div class="note info flat"><p><strong>ID组成结构</strong>：符号位 + 时间戳 + 序列号，总计64位</p></div><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">┌──符号位──┬───────时间戳───────┬───────序列号───────┐</span><br><span class="line">│    1bit   │      31bit       │      32bit       │</span><br><span class="line">│    永远0   │  秒级时间戳(69年)  │  秒内计数器(2^32)  │</span><br><span class="line">└──────────┴─────────────────┴─────────────────┘</span><br></pre></td></tr></table></figure><p><strong>设计优势</strong>：</p><ul><li>✅ <strong>符号位</strong>：1bit，永远为0，保证ID为正数</li><li>✅ <strong>时间戳</strong>：31bit，以秒为单位，可以使用69年</li><li>✅ <strong>序列号</strong>：32bit，秒内的计数器，支持每秒产生2^32个不同ID</li><li>✅ <strong>安全性</strong>：不直接使用Redis自增数值，避免泄露业务量</li></ul><h4 id="3-1-2-Redis实现方案">3.1.2 Redis实现方案</h4><div class="tabs" id="redis-id-implementation"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#redis-id-implementation-1">核心代码</button></li><li class="tab"><button type="button" data-href="#redis-id-implementation-2">代码解析</button></li><li class="tab"><button type="button" data-href="#redis-id-implementation-3">测试验证</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="redis-id-implementation-1"><p><strong>RedisIdWorker工具类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisIdWorker</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 开始时间戳 - 2022-01-01 00:00:00</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">BEGIN_TIMESTAMP</span> <span class="operator">=</span> <span class="number">1640995200L</span>;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 序列号的位数</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">COUNT_BITS</span> <span class="operator">=</span> <span class="number">32</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">RedisIdWorker</span><span class="params">(StringRedisTemplate stringRedisTemplate)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.stringRedisTemplate = stringRedisTemplate;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 生成全局唯一ID</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> keyPrefix 业务前缀</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 全局唯一ID</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">nextId</span><span class="params">(String keyPrefix)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 生成时间戳</span></span><br><span class="line">        <span class="type">LocalDateTime</span> <span class="variable">now</span> <span class="operator">=</span> LocalDateTime.now();</span><br><span class="line">        <span class="type">long</span> <span class="variable">nowSecond</span> <span class="operator">=</span> now.toEpochSecond(ZoneOffset.UTC);</span><br><span class="line">        <span class="type">long</span> <span class="variable">timestamp</span> <span class="operator">=</span> nowSecond - BEGIN_TIMESTAMP;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 生成序列号 - Redis自增</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">date</span> <span class="operator">=</span> now.format(DateTimeFormatter.ofPattern(<span class="string">&quot;yyyy:MM:dd&quot;</span>));</span><br><span class="line">        <span class="type">Long</span> <span class="variable">count</span> <span class="operator">=</span> stringRedisTemplate.opsForValue()</span><br><span class="line">                .increment(<span class="string">&quot;icr:&quot;</span> + keyPrefix + <span class="string">&quot;:&quot;</span> + date);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. 拼接并返回</span></span><br><span class="line">        <span class="keyword">return</span> timestamp &lt;&lt; COUNT_BITS | count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redis-id-implementation-2"><p><strong>实现原理解析</strong></p><table><thead><tr><th>步骤</th><th>操作</th><th>说明</th></tr></thead><tbody><tr><td>1</td><td>生成时间戳</td><td>当前时间 - 起始时间(2022-01-01)</td></tr><tr><td>2</td><td>生成序列号</td><td>Redis自增，按天分组避免溢出</td></tr><tr><td>3</td><td>位运算拼接</td><td><code>timestamp &lt;&lt; 32 | count</code></td></tr></tbody></table><p><strong>关键设计</strong>：</p><ul><li>✅ <strong>时间戳基准</strong>：使用2022-01-01作为起始时间，支持69年使用期</li><li>✅ <strong>按天分片</strong>：Redis key包含日期，避免自增数值过大</li><li>✅ <strong>位运算优化</strong>：使用位移和或运算快速拼接ID</li><li>✅ <strong>线程安全</strong>：Redis自增操作保证并发安全</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redis-id-implementation-3"><p><strong>性能测试代码</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testIdWorker</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">    <span class="type">CountDownLatch</span> <span class="variable">latch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(<span class="number">300</span>);</span><br><span class="line">    <span class="type">Runnable</span> <span class="variable">task</span> <span class="operator">=</span> () -&gt; &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">            <span class="type">long</span> <span class="variable">id</span> <span class="operator">=</span> redisIdWorker.nextId(<span class="string">&quot;test&quot;</span>);</span><br><span class="line">            System.out.println(<span class="string">&quot;id = &quot;</span> + id);</span><br><span class="line">        &#125;</span><br><span class="line">        latch.countDown();</span><br><span class="line">    &#125;;</span><br><span class="line">    </span><br><span class="line">    <span class="type">long</span> <span class="variable">begin</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">    <span class="comment">// 300个线程，每个线程生成100个ID</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">300</span>; i++) &#123;</span><br><span class="line">        es.submit(task);</span><br><span class="line">    &#125;</span><br><span class="line">    latch.await();</span><br><span class="line">    <span class="type">long</span> <span class="variable">end</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">    System.out.println(<span class="string">&quot;time = &quot;</span> + (end - begin));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>测试结果</strong>：</p><ul><li>🚀 <strong>生成速度</strong>：3万个ID仅需几百毫秒</li><li>🚀 <strong>并发安全</strong>：多线程环境下无重复ID</li><li>🚀 <strong>趋势递增</strong>：ID整体呈递增趋势</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h4 id="3-1-3-方案对比总结">3.1.3 方案对比总结</h4><div class="note success flat"><p><strong>Redis方案 vs 其他方案</strong></p></div><table><thead><tr><th>方案</th><th>实现复杂度</th><th>性能</th><th>可用性</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>Redis方案</strong></td><td>⭐⭐ 简单</td><td>⭐⭐⭐⭐⭐ 10W+QPS</td><td>⭐⭐⭐⭐ 主从架构</td><td>中小型系统</td></tr><tr><td><strong>雪花算法</strong></td><td>⭐⭐⭐ 中等</td><td>⭐⭐⭐⭐⭐ 更高</td><td>⭐⭐ 依赖时钟</td><td>大型分布式系统</td></tr><tr><td><strong>数据库自增</strong></td><td>⭐ 最简单</td><td>⭐⭐ 千级QPS</td><td>⭐⭐ 单点故障</td><td>单机系统</td></tr><tr><td><strong>UUID</strong></td><td>⭐ 简单</td><td>⭐⭐⭐ 中等</td><td>⭐⭐⭐⭐⭐ 完全分布式</td><td>对顺序无要求场景</td></tr></tbody></table><h3 id="3-2-优惠券管理">3.2 优惠券管理</h3><h4 id="3-2-1-优惠券类型设计">3.2.1 优惠券类型设计</h4><div class="tabs" id="voucher-types"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#voucher-types-1">业务场景</button></li><li class="tab"><button type="button" data-href="#voucher-types-2">数据模型</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="voucher-types-1"><p><strong>优惠券业务模型</strong></p><table><thead><tr><th>类型</th><th>特点</th><th>使用场景</th><th>表结构</th></tr></thead><tbody><tr><td><strong>普通券</strong></td><td>优惠力度小，任意领取</td><td>日常促销</td><td>tb_voucher</td></tr><tr><td><strong>秒杀券</strong></td><td>优惠力度大，限时限量</td><td>引流获客</td><td>tb_voucher + tb_seckill_voucher</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="voucher-types-2"><p><strong>表结构设计</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 优惠券基础信息表</span></span><br><span class="line">tb_voucher (</span><br><span class="line">    id <span class="type">bigint</span> <span class="keyword">PRIMARY KEY</span>,          <span class="comment">-- 优惠券ID</span></span><br><span class="line">    shop_id <span class="type">bigint</span>,                 <span class="comment">-- 商铺ID</span></span><br><span class="line">    title <span class="type">varchar</span>(<span class="number">255</span>),             <span class="comment">-- 优惠券标题</span></span><br><span class="line">    sub_title <span class="type">varchar</span>(<span class="number">255</span>),         <span class="comment">-- 副标题</span></span><br><span class="line">    rules <span class="type">varchar</span>(<span class="number">1024</span>),            <span class="comment">-- 使用规则</span></span><br><span class="line">    pay_value <span class="type">bigint</span>,               <span class="comment">-- 支付金额（分）</span></span><br><span class="line">    actual_value <span class="type">bigint</span>,            <span class="comment">-- 抵扣金额（分）</span></span><br><span class="line">    type tinyint,                   <span class="comment">-- 类型：1-普通券，2-秒杀券</span></span><br><span class="line">    status tinyint,                 <span class="comment">-- 状态：1-上架，2-下架，3-过期</span></span><br><span class="line">    create_time datetime,           <span class="comment">-- 创建时间</span></span><br><span class="line">    update_time datetime            <span class="comment">-- 更新时间</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 秒杀优惠券扩展表</span></span><br><span class="line">tb_seckill_voucher (</span><br><span class="line">    voucher_id <span class="type">bigint</span> <span class="keyword">PRIMARY KEY</span>,  <span class="comment">-- 优惠券ID</span></span><br><span class="line">    stock <span class="type">int</span>,                      <span class="comment">-- 库存</span></span><br><span class="line">    create_time datetime,           <span class="comment">-- 创建时间</span></span><br><span class="line">    begin_time datetime,            <span class="comment">-- 开始抢购时间</span></span><br><span class="line">    end_time datetime               <span class="comment">-- 结束抢购时间</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h4 id="3-2-2-优惠券发布实现">3.2.2 优惠券发布实现</h4><div class="tabs" id="voucher-implementation"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#voucher-implementation-1">普通券发布</button></li><li class="tab"><button type="button" data-href="#voucher-implementation-2">秒杀券发布</button></li><li class="tab"><button type="button" data-href="#voucher-implementation-3">前端调用服务</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="voucher-implementation-1"><p><strong>Controller层接口</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 新增普通券</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> voucher 优惠券信息</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 优惠券id</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@PostMapping</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">addVoucher</span><span class="params">(<span class="meta">@RequestBody</span> Voucher voucher)</span> &#123;</span><br><span class="line">    voucherService.save(voucher);</span><br><span class="line">    <span class="keyword">return</span> Result.ok(voucher.getId());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="voucher-implementation-2"><p><strong>Controller层接口</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 新增秒杀券</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> voucher 优惠券信息，包含秒杀信息</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 优惠券id</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@PostMapping(&quot;seckill&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">addSeckillVoucher</span><span class="params">(<span class="meta">@RequestBody</span> Voucher voucher)</span> &#123;</span><br><span class="line">    voucherService.addSeckillVoucher(voucher);</span><br><span class="line">    <span class="keyword">return</span> Result.ok(voucher.getId());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Service层实现</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addSeckillVoucher</span><span class="params">(Voucher voucher)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 保存优惠券基础信息</span></span><br><span class="line">    save(voucher);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 保存秒杀扩展信息</span></span><br><span class="line">    <span class="type">SeckillVoucher</span> <span class="variable">seckillVoucher</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SeckillVoucher</span>();</span><br><span class="line">    seckillVoucher.setVoucherId(voucher.getId());</span><br><span class="line">    seckillVoucher.setStock(voucher.getStock());</span><br><span class="line">    seckillVoucher.setBeginTime(voucher.getBeginTime());</span><br><span class="line">    seckillVoucher.setEndTime(voucher.getEndTime());</span><br><span class="line">    seckillVoucherService.save(seckillVoucher);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 同步库存到Redis</span></span><br><span class="line">    stringRedisTemplate.opsForValue()</span><br><span class="line">        .set(SECKILL_STOCK_KEY + voucher.getId(), voucher.getStock().toString());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>设计要点</strong>：</p><ul><li>✅ <strong>事务控制</strong>：使用<code>@Transactional</code>保证数据一致性</li><li>✅ <strong>Redis同步</strong>：将库存信息缓存到Redis，提升查询性能</li><li>✅ <strong>时间窗口</strong>：设置秒杀开始和结束时间</li><li>✅ <strong>库存隔离</strong>：秒杀券有独立库存管理</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="voucher-implementation-3"><p><strong>用POSTMAN模拟发送请求来新增秒杀券，请求路径http://localhost:8081/voucher/seckill， 请求方式POST，JSON数据如下</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;shopId&quot;</span><span class="punctuation">:</span><span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span><span class="string">&quot;100元代金券&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;subTitle&quot;</span><span class="punctuation">:</span><span class="string">&quot;周一至周五可用&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;rules&quot;</span><span class="punctuation">:</span><span class="string">&quot;全场通用\\n无需预约\\n可无限叠加&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;payValue&quot;</span><span class="punctuation">:</span><span class="number">8000</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;actualValue&quot;</span><span class="punctuation">:</span><span class="number">10000</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span><span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;stock&quot;</span><span class="punctuation">:</span><span class="number">100</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;beginTime&quot;</span><span class="punctuation">:</span><span class="string">&quot;2022-01-01T00:00:00&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;endTime&quot;</span><span class="punctuation">:</span><span class="string">&quot;2022-10-31T23:59:59&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="3-3-秒杀下单实现">3.3 秒杀下单实现</h3><h4 id="3-3-1-业务需求分析">3.3.1 业务需求分析</h4><div class="tabs" id="seckill-requirement"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#seckill-requirement-1">业务流程</button></li><li class="tab"><button type="button" data-href="#seckill-requirement-2">前置条件</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="seckill-requirement-1"><p><strong>秒杀下单核心逻辑</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">graph TD</span><br><span class="line">    A[用户点击抢购] --&gt; B[查询优惠券信息]</span><br><span class="line">    B --&gt; C&#123;秒杀是否开始?&#125;</span><br><span class="line">    C --&gt;|未开始| D[返回失败:秒杀尚未开始]</span><br><span class="line">    C --&gt;|已开始| E&#123;秒杀是否结束?&#125;</span><br><span class="line">    E --&gt;|已结束| F[返回失败:秒杀已结束]</span><br><span class="line">    E --&gt;|进行中| G&#123;库存是否充足?&#125;</span><br><span class="line">    G --&gt;|不足| H[返回失败:库存不足]</span><br><span class="line">    G --&gt;|充足| I[扣减库存]</span><br><span class="line">    I --&gt; J[创建订单]</span><br><span class="line">    J --&gt; K[返回订单ID]</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="seckill-requirement-2"><p><strong>下单校验规则</strong></p><table><thead><tr><th>校验项</th><th>判断条件</th><th>失败提示</th></tr></thead><tbody><tr><td>秒杀时间</td><td>beginTime &gt; 当前时间</td><td>秒杀尚未开始！</td></tr><tr><td>秒杀时间</td><td>endTime &lt; 当前时间</td><td>秒杀已结束！</td></tr><tr><td>库存检查</td><td>stock &lt; 1</td><td>库存不足！</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h4 id="3-3-2-基础实现方案">3.3.2 基础实现方案</h4><div class="tabs" id="seckill-basic"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#seckill-basic-1">controller层调用</button></li><li class="tab"><button type="button" data-href="#seckill-basic-2">Service</button></li><li class="tab"><button type="button" data-href="#seckill-basic-3">Service层实现</button></li><li class="tab"><button type="button" data-href="#seckill-basic-4">问题分析</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="seckill-basic-1"><p><strong>VoucherController</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/voucher-order&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VoucherOrderController</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> IVoucherOrderService voucherOrderService;</span><br><span class="line">    <span class="meta">@PostMapping(&quot;/seckill/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">seckillVoucher</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long voucherId)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> voucherOrderService.seckillVoucher(voucherId);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="seckill-basic-2"><p><strong>VoucherOrderService</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">IVoucherOrderService</span> <span class="keyword">extends</span> <span class="title class_">IService</span>&lt;VoucherOrder&gt; &#123;</span><br><span class="line">    Result <span class="title function_">seckillVoucher</span><span class="params">(Long voucherId)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="seckill-basic-3"><p><strong>VoucherOrderServiceImpl</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> ISeckillVoucherService seckillVoucherService;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> RedisIdWorker redisIdWorker;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">seckillVoucher</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 查询优惠券信息</span></span><br><span class="line">    <span class="type">SeckillVoucher</span> <span class="variable">voucher</span> <span class="operator">=</span> seckillVoucherService.getById(voucherId);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 判断秒杀是否开始</span></span><br><span class="line">    <span class="keyword">if</span> (voucher.getBeginTime().isAfter(LocalDateTime.now())) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;秒杀尚未开始！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 判断秒杀是否已经结束</span></span><br><span class="line">    <span class="keyword">if</span> (voucher.getEndTime().isBefore(LocalDateTime.now())) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;秒杀已经结束！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4. 判断库存是否充足</span></span><br><span class="line">    <span class="keyword">if</span> (voucher.getStock() &lt; <span class="number">1</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;库存不足！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 5. 扣减库存</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> seckillVoucherService.update()</span><br><span class="line">            .setSql(<span class="string">&quot;stock = stock - 1&quot;</span>)</span><br><span class="line">            .eq(<span class="string">&quot;voucher_id&quot;</span>, voucherId)</span><br><span class="line">            .update();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (!success) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;库存不足！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 6. 创建订单</span></span><br><span class="line">    <span class="type">VoucherOrder</span> <span class="variable">voucherOrder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">VoucherOrder</span>();</span><br><span class="line">    <span class="comment">// 6.1 订单ID</span></span><br><span class="line">    <span class="type">long</span> <span class="variable">orderId</span> <span class="operator">=</span> redisIdWorker.nextId(<span class="string">&quot;order&quot;</span>);</span><br><span class="line">    voucherOrder.setId(orderId);</span><br><span class="line">    <span class="comment">// 6.2 用户ID</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    voucherOrder.setUserId(userId);</span><br><span class="line">    <span class="comment">// 6.3 代金券ID</span></span><br><span class="line">    voucherOrder.setVoucherId(voucherId);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 7. 保存订单</span></span><br><span class="line">    save(voucherOrder);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 8. 返回订单ID</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok(orderId);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="seckill-basic-4"><p><strong>当前方案存在的问题</strong></p><div class="note warning flat"><p><strong>高并发下的问题分析</strong></p></div><table><thead><tr><th>问题</th><th>现象</th><th>原因</th><th>影响</th></tr></thead><tbody><tr><td><strong>超卖问题</strong></td><td>库存为负</td><td>并发扣减库存</td><td>商家损失</td></tr><tr><td><strong>重复下单</strong></td><td>同一用户多单</td><td>并发创建订单</td><td>用户体验差</td></tr><tr><td><strong>性能瓶颈</strong></td><td>响应慢</td><td>数据库压力大</td><td>系统崩溃</td></tr></tbody></table><p><strong>问题根因</strong>：</p><ul><li>❌ <strong>无并发控制</strong>：多个线程同时扣减库存</li><li>❌ <strong>无幂等保障</strong>：同一用户可重复下单</li><li>❌ <strong>数据库压力大</strong>：所有操作都走数据库</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="3-4-库存超卖问题分析">3.4 库存超卖问题分析</h3><div class="tabs" id="oversell-analysis"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#oversell-analysis-1">问题复现</button></li><li class="tab"><button type="button" data-href="#oversell-analysis-2">解决方案对比</button></li><li class="tab"><button type="button" data-href="#oversell-analysis-3">乐观锁优化</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="oversell-analysis-1"><p><strong>并发场景下的超卖问题</strong></p><p>假设库存为1，同时有100个线程并发下单：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">时间点 | 线程1 | 线程2 | 线程3 | ... | 库存</span><br><span class="line">-------|-------|-------|-------|-----|-----</span><br><span class="line">t1     | 查询库存=1 | 查询库存=1 | 查询库存=1 | ... | 1</span><br><span class="line">t2     | 判断库存&gt;0✅ | 判断库存&gt;0✅ | 判断库存&gt;0✅ | ... | 1</span><br><span class="line">t3     | 扣减库存 | 扣减库存 | 扣减库存 | ... | 1→0→-1→-2...</span><br></pre></td></tr></table></figure><p><strong>问题本质</strong>：</p><ul><li>❌ <strong>读-改-写</strong>操作非原子性</li><li>❌ <strong>并发控制缺失</strong>导致数据竞争</li><li>❌ <strong>库存校验</strong>与<strong>库存扣减</strong>分离</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="oversell-analysis-2"><p><strong>悲观锁 vs 乐观锁</strong></p><div class="note info flat"><p><strong>悲观锁</strong>：假定会发生并发冲突，屏蔽一切可能违反数据完整性的操作</p></div><div class="note info flat"><p><strong>乐观锁</strong>：认为线程安全问题不一定会发生，因此不加锁，只是在更新数据的时候再去判断有没有其他线程对数据进行了修改</p></div><table><thead><tr><th>方案</th><th>实现方式</th><th>优点</th><th>缺点</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>悲观锁</strong></td><td><code>synchronized</code>/<code>ReentrantLock</code></td><td>简单易理解</td><td>性能差，阻塞严重</td><td>并发量低的场景</td></tr><tr><td><strong>乐观锁</strong></td><td>版本号机制/CAS</td><td>性能好，无阻塞</td><td>实现复杂，ABA问题</td><td>并发量高的场景</td></tr></tbody></table><p><strong>乐观锁实现原理</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 版本号机制</span></span><br><span class="line">UPDATE tb_seckill_voucher </span><br><span class="line"><span class="type">SET</span> <span class="variable">stock</span> <span class="operator">=</span> stock - <span class="number">1</span>, version = version + <span class="number">1</span> </span><br><span class="line"><span class="type">WHERE</span> <span class="variable">voucher_id</span> <span class="operator">=</span> ? <span class="type">AND</span> <span class="variable">version</span> <span class="operator">=</span> ?</span><br><span class="line"></span><br><span class="line"><span class="comment">// CAS机制（库存大于0）  </span></span><br><span class="line">UPDATE tb_seckill_voucher </span><br><span class="line"><span class="type">SET</span> <span class="variable">stock</span> <span class="operator">=</span> stock - <span class="number">1</span> </span><br><span class="line"><span class="type">WHERE</span> <span class="variable">voucher_id</span> <span class="operator">=</span> ? AND stock &gt; <span class="number">0</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="oversell-analysis-3"><p><strong>乐观锁方案演进</strong></p><div class="note warning flat"><p><strong>方案一：版本号控制</strong>（成功率低）</p></div><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> seckillVoucherService.update()</span><br><span class="line">    .setSql(<span class="string">&quot;stock = stock - 1&quot;</span>)</span><br><span class="line">    .eq(<span class="string">&quot;voucher_id&quot;</span>, voucherId)</span><br><span class="line">    .eq(<span class="string">&quot;stock&quot;</span>, voucher.getStock())  <span class="comment">// 版本号校验</span></span><br><span class="line">    .update();</span><br></pre></td></tr></table></figure><p><strong>问题分析</strong>：</p><ul><li>❌ <strong>成功率极低</strong>：100个线程同时拿到stock=100，只有1个能成功</li><li>❌ <strong>重试压力大</strong>：失败线程需要重新查询版本号再重试</li><li>❌ <strong>用户体验差</strong>：大量请求返回失败</li></ul><div class="note success flat"><p><strong>方案二：库存大于0控制</strong>（推荐）</p></div><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> seckillVoucherService.update()</span><br><span class="line">    .setSql(<span class="string">&quot;stock = stock - 1&quot;</span>)</span><br><span class="line">    .eq(<span class="string">&quot;voucher_id&quot;</span>, voucherId)</span><br><span class="line">    .gt(<span class="string">&quot;stock&quot;</span>, <span class="number">0</span>)  <span class="comment">// 只需保证库存大于0</span></span><br><span class="line">    .update();</span><br></pre></td></tr></table></figure><p><strong>优势分析</strong>：</p><ul><li>✅ <strong>成功率高</strong>：只要还有库存就能成功</li><li>✅ <strong>实现简单</strong>：无需维护版本号</li><li>✅ <strong>性能优秀</strong>：无自旋重试，一次操作完成</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="3-5-一人一单问题">3.5 一人一单问题</h3><div class="tabs" id="one-user-one-order"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#one-user-one-order-1">业务需求</button></li><li class="tab"><button type="button" data-href="#one-user-one-order-2">并发问题</button></li><li class="tab"><button type="button" data-href="#one-user-one-order-3">解决方案</button></li><li class="tab"><button type="button" data-href="#one-user-one-order-4">事务问题</button></li><li class="tab"><button type="button" data-href="#one-user-one-order-5">引入配置和依赖</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="one-user-one-order-1"><p><strong>需求分析</strong></p><div class="note primary flat"><p><strong>业务规则</strong>：同一个优惠券，一个用户只能下一单</p></div><p><strong>问题背景</strong>：</p><ul><li>🎯 <strong>营销目的</strong>：优惠券用于引流获客，需要控制成本</li><li>🎯 <strong>公平性</strong>：防止黄牛党恶意刷单</li><li>🎯 <strong>用户体验</strong>：让更多用户享受到优惠</li></ul><p><strong>实现思路</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">用户下单前 → 查询该用户是否已购买 → 已购买则拒绝 → 未购买则允许下单</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="one-user-one-order-2"><p><strong>并发场景下的问题</strong></p><p>假设用户A同时发起5个请求：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">时间点 | 请求1 | 请求2 | 请求3 | 请求4 | 请求5</span><br><span class="line">-------|-------|-------|-------|-------|------</span><br><span class="line">t1     | 查询订单=0✅ | 查询订单=0✅ | 查询订单=0✅ | 查询订单=0✅ | 查询订单=0✅</span><br><span class="line">t2     | 创建订单 | 创建订单 | 创建订单 | 创建订单 | 创建订单</span><br><span class="line">t3     | 成功 | 成功 | 成功 | 成功 | 成功</span><br></pre></td></tr></table></figure><p><strong>问题本质</strong>：</p><ul><li>❌ <strong>查询-判断-创建</strong>操作非原子性</li><li>❌ <strong>无并发控制</strong>导致重复下单</li><li>❌ <strong>数据库唯一约束</strong>无法防止并发插入</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="one-user-one-order-3"><p><strong>悲观锁解决方案</strong></p><div class="note success flat"><p><strong>核心思路</strong>：对<strong>用户ID</strong>加锁，保证同一用户并发请求串行处理</p></div><p><strong>代码实现</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">seckillVoucher</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 查询优惠券信息</span></span><br><span class="line">    <span class="type">SeckillVoucher</span> <span class="variable">voucher</span> <span class="operator">=</span> seckillVoucherService.getById(voucherId);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 判断秒杀是否开始</span></span><br><span class="line">    <span class="keyword">if</span> (voucher.getBeginTime().isAfter(LocalDateTime.now())) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;秒杀尚未开始！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 判断秒杀是否已经结束</span></span><br><span class="line">    <span class="keyword">if</span> (voucher.getEndTime().isBefore(LocalDateTime.now())) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;秒杀已经结束！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4. 判断库存是否充足</span></span><br><span class="line">    <span class="keyword">if</span> (voucher.getStock() &lt; <span class="number">1</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;库存不足！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 5. 一人一单控制</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="keyword">synchronized</span> (userId.toString().intern()) &#123;        </span><br><span class="line">        <span class="comment">// 创建订单（包含一人一单校验扣减库存逻辑）</span></span><br><span class="line">        <span class="keyword">return</span> createVoucherOrder(voucherId);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> Result <span class="title function_">createVoucherOrder</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line">    <span class="comment">// 一人一单逻辑</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> query().eq(<span class="string">&quot;voucher_id&quot;</span>, voucherId).eq(<span class="string">&quot;user_id&quot;</span>, userId).count();</span><br><span class="line">    <span class="keyword">if</span> (count &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;你已经抢过优惠券了哦&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//5. 扣减库存</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> seckillVoucherService.update()</span><br><span class="line">            .setSql(<span class="string">&quot;stock = stock - 1&quot;</span>)</span><br><span class="line">            .eq(<span class="string">&quot;voucher_id&quot;</span>, voucherId)</span><br><span class="line">            .gt(<span class="string">&quot;stock&quot;</span>, <span class="number">0</span>)</span><br><span class="line">            .update();</span><br><span class="line">    <span class="keyword">if</span> (!success) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;库存不足&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//6. 创建订单</span></span><br><span class="line">    <span class="type">VoucherOrder</span> <span class="variable">voucherOrder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">VoucherOrder</span>();</span><br><span class="line">    <span class="comment">//6.1 设置订单id</span></span><br><span class="line">    <span class="type">long</span> <span class="variable">orderId</span> <span class="operator">=</span> redisIdWorker.nextId(<span class="string">&quot;order&quot;</span>);</span><br><span class="line">    <span class="comment">//6.2 设置用户id</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">id</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="comment">//6.3 设置代金券id</span></span><br><span class="line">    voucherOrder.setVoucherId(voucherId);</span><br><span class="line">    voucherOrder.setId(orderId);</span><br><span class="line">    voucherOrder.setUserId(id);</span><br><span class="line">    <span class="comment">//7. 将订单数据保存到表中</span></span><br><span class="line">    save(voucherOrder);</span><br><span class="line">    <span class="comment">//8. 返回订单id</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok(orderId);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键设计</strong>：</p><ul><li>✅ <strong>锁粒度</strong>：按用户ID加锁，不同用户互不影响</li><li>✅ <strong>字符串常量池</strong>：使用<code>intern()</code>确保同一把锁</li><li>✅ <strong>事务边界</strong>：锁要包裹整个事务操作</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="one-user-one-order-4"><p><strong>事务与锁的协同机制</strong></p><div class="note warning flat"><p><strong>核心问题</strong>：Spring事务与JVM锁的生命周期不一致</p></div><p><strong>问题根源</strong>：<br>直接通过<code>this</code>调用同类方法会导致Spring事务失效，因为事务是通过AOP代理机制实现的。Spring的事务管理基于动态代理，只有通过代理对象调用的方法才能被事务拦截器处理。</p><p><strong>生命周期冲突</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">method</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">synchronized</span>(lock) &#123;</span><br><span class="line">        <span class="comment">// 业务操作</span></span><br><span class="line">    &#125; <span class="comment">// 🔒锁已释放，但事务还未提交</span></span><br><span class="line">&#125; <span class="comment">// 💥事务提交，但其他线程可能已获取锁并读取到脏数据</span></span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：获取Spring代理对象确保事务生效<br>使用<code>AopContext.currentProxy()</code>获取当前代理对象，通过代理对象调用事务方法，确保锁在事务提交后才释放。</p><p><strong>技术实现要点</strong>：</p><ol><li>启用暴露代理：<code>@EnableAspectJAutoProxy(exposeProxy = true)</code></li><li>接口定义：在<code>IVoucherOrderService</code>接口中声明<code>createVoucherOrder</code>方法</li><li>代理调用：通过代理对象调用确保事务拦截器生效</li></ol><p><strong>解决方案</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Result <span class="title function_">seckillVoucher</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line">    <span class="comment">// 前置校验方案</span></span><br><span class="line"></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="keyword">synchronized</span> (userId.toString().intern()) &#123;</span><br><span class="line">        <span class="comment">// 获取代理对象，确保事务生效</span></span><br><span class="line">        <span class="type">IVoucherOrderService</span> <span class="variable">proxy</span> <span class="operator">=</span> (IVoucherOrderService) AopContext.currentProxy();</span><br><span class="line">        <span class="keyword">return</span> proxy.createVoucherOrder(voucherId);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">createVoucherOrder</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line">    <span class="comment">// 事务操作</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line"><span class="keyword">synchronized</span>(userId.toString().intern())&#123;</span><br><span class="line">         <span class="comment">// 5.1.查询订单</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> query().eq(<span class="string">&quot;user_id&quot;</span>, userId).eq(<span class="string">&quot;voucher_id&quot;</span>, voucherId).count();</span><br><span class="line">        <span class="comment">// 5.2.判断是否存在</span></span><br><span class="line">        <span class="keyword">if</span> (count &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 用户已经购买过了</span></span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;用户已经购买过一次！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 6.扣减库存</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> seckillVoucherService.update()</span><br><span class="line">                .setSql(<span class="string">&quot;stock = stock - 1&quot;</span>) <span class="comment">// set stock = stock - 1</span></span><br><span class="line">                .eq(<span class="string">&quot;voucher_id&quot;</span>, voucherId).gt(<span class="string">&quot;stock&quot;</span>, <span class="number">0</span>) <span class="comment">// where id = ? and stock &gt; 0</span></span><br><span class="line">                .update();</span><br><span class="line">        <span class="keyword">if</span> (!success) &#123;</span><br><span class="line">            <span class="comment">// 扣减失败</span></span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;库存不足！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 7.创建订单</span></span><br><span class="line">        <span class="type">VoucherOrder</span> <span class="variable">voucherOrder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">VoucherOrder</span>();</span><br><span class="line">        <span class="comment">// 7.1.订单id</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">orderId</span> <span class="operator">=</span> redisIdWorker.nextId(<span class="string">&quot;order&quot;</span>);</span><br><span class="line">        voucherOrder.setId(orderId);</span><br><span class="line">        <span class="comment">// 7.2.用户id</span></span><br><span class="line">        voucherOrder.setUserId(userId);</span><br><span class="line">        <span class="comment">// 7.3.代金券id</span></span><br><span class="line">        voucherOrder.setVoucherId(voucherId);</span><br><span class="line">        save(voucherOrder);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 7.返回订单id</span></span><br><span class="line">        <span class="keyword">return</span> Result.ok(orderId);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="one-user-one-order-5"><p><strong>引入配置和依赖</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 引入aspectjweaver依赖</span></span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.aspectj&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;aspectjweaver&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在启动类上加上@EnableAspectJAutoProxy(exposeProxy = true)注解</span></span><br><span class="line"><span class="meta">@MapperScan(&quot;com.hmdp.mapper&quot;)</span></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableAspectJAutoProxy(exposeProxy = true)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HmDianPingApplication</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        SpringApplication.run(HmDianPingApplication.class, args);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="3-6-集群环境下的并发问题">3.6 集群环境下的并发问题</h3><div class="tabs" id="cluster-concurrency"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#cluster-concurrency-1">问题分析</button></li><li class="tab"><button type="button" data-href="#cluster-concurrency-2">分布式锁需求</button></li><li class="tab"><button type="button" data-href="#cluster-concurrency-3">Redis分布式锁</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="cluster-concurrency-1"><p><strong>集群模式下的新问题</strong></p><div class="note danger flat"><p><strong>JVM锁失效</strong>：集群环境下，每个JVM实例有自己的锁</p></div><p><strong>场景复现</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">用户A的请求 → Nginx负载均衡 → 8081端口（JVM锁生效）</span><br><span class="line">用户A的请求 → Nginx负载均衡 → 8082端口（JVM锁失效）</span><br></pre></td></tr></table></figure><p><strong>问题本质</strong>：</p><ul><li>❌ <strong>JVM锁作用域</strong>：只在单个JVM实例内有效</li><li>❌ <strong>负载均衡</strong>：同一用户的请求可能分发到不同实例</li><li>❌ <strong>分布式环境</strong>：需要<strong>分布式锁</strong>解决方案</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="cluster-concurrency-2"><p><strong>分布式锁核心要求</strong></p><div class="note info flat"><p><strong>分布式锁</strong>：在分布式系统中，所有节点共享的锁机制</p></div><table><thead><tr><th>特性</th><th>要求</th><th>实现方案</th></tr></thead><tbody><tr><td><strong>互斥性</strong></td><td>同一时间只有一个客户端能获取锁</td><td>Redis SETNX</td></tr><tr><td><strong>安全性</strong></td><td>锁只能被持有者释放</td><td>唯一标识 + Lua脚本</td></tr><tr><td><strong>死锁避免</strong></td><td>锁必须有超时时间</td><td>Redis EXPIRE</td></tr><tr><td><strong>可用性</strong></td><td>高可用的锁服务</td><td>Redis集群</td></tr><tr><td><strong>可重入性</strong></td><td>同一客户端可重复获取锁</td><td>ThreadLocal + 计数器</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="cluster-concurrency-3"><p><strong>Redis分布式锁实现</strong></p><p><strong>基本命令</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 获取锁（SETNX + EXPIRE）</span></span><br><span class="line">SET lock_key unique_value NX EX 30</span><br><span class="line"></span><br><span class="line"><span class="comment"># 释放锁（Lua脚本保证原子性）</span></span><br><span class="line"><span class="keyword">if</span> redis.call(<span class="string">&quot;get&quot;</span>, KEYS[1]) == ARGV[1] <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">return</span> redis.call(<span class="string">&quot;del&quot;</span>, KEYS[1])</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    <span class="built_in">return</span> 0</span><br><span class="line">end</span><br></pre></td></tr></table></figure><p><strong>实现优势</strong>：</p><ul><li>✅ <strong>高性能</strong>：Redis内存操作，10W+QPS</li><li>✅ <strong>高可用</strong>：Redis主从架构，故障自动切换</li><li>✅ <strong>易实现</strong>：命令简单，客户端支持好</li><li>✅ <strong>可扩展</strong>：支持RedLock算法，多Redis实例</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><div class="note info flat"><p><strong>有关锁失效原因分析</strong>：这就是集群环境下，syn锁失效的原因，在这种情况下，我们需要使用分布式锁来解决这个问题，让锁不存在于每个jvm的内部，而是让所有jvm公用外部的一把锁（Redis）</p></div><h2 id="4-分布式锁">4. 分布式锁</h2><h3 id="4-1-分布式锁概述">4.1 分布式锁概述</h3><div class="tabs" id="distributed-lock-overview"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#distributed-lock-overview-1">基本概念</button></li><li class="tab"><button type="button" data-href="#distributed-lock-overview-2">核心要求</button></li><li class="tab"><button type="button" data-href="#distributed-lock-overview-3">实现方案对比</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="distributed-lock-overview-1"><p><strong>什么是分布式锁</strong></p><div class="note primary flat"><p><strong>分布式锁</strong>：在分布式系统或集群模式下，多个进程可见且互斥的锁机制</p></div><p><strong>核心思想</strong>：所有节点使用<strong>同一把锁</strong>，确保程序串行执行</p><p><strong>与JVM锁的区别</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">JVM锁：只在单个JVM实例内有效（synchronized、ReentrantLock）</span><br><span class="line">分布式锁：在多个节点间共享，所有实例都能感知锁状态</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="distributed-lock-overview-2"><p><strong>分布式锁必备特性</strong></p><table><thead><tr><th>特性</th><th>说明</th><th>实现方案</th></tr></thead><tbody><tr><td><strong>互斥性</strong></td><td>同一时间只有一个客户端能获取锁</td><td>Redis SETNX</td></tr><tr><td><strong>可见性</strong></td><td>所有节点都能感知锁状态变化</td><td>Redis发布订阅</td></tr><tr><td><strong>高可用</strong></td><td>锁服务不易崩溃，故障可恢复</td><td>Redis集群/哨兵</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>TTL过期机制</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="distributed-lock-overview-3"><p><strong>常见分布式锁实现</strong></p><div class="note info flat"><p><strong>三种主流方案对比</strong></p></div><table><thead><tr><th>方案</th><th>优点</th><th>缺点</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>MySQL</strong></td><td>实现简单，事务支持</td><td>性能差，锁表风险</td><td>低频操作</td></tr><tr><td><strong>Redis</strong></td><td>高性能，10W+QPS</td><td>需要处理锁超时</td><td>高频并发</td></tr><tr><td><strong>Zookeeper</strong></td><td>强一致性，Watch机制</td><td>实现复杂，性能一般</td><td>强一致性要求</td></tr></tbody></table><p><strong>企业级选择</strong>：</p><ul><li>✅ <strong>Redis</strong>：99%场景的首选（性能+可用性平衡）</li><li>✅ <strong>Redisson</strong>：Java生态最成熟的分布式锁框架</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="4-2-Redis分布式锁实现">4.2 Redis分布式锁实现</h3><div class="tabs" id="redis-distributed-lock"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#redis-distributed-lock-1">核心命令</button></li><li class="tab"><button type="button" data-href="#redis-distributed-lock-2">代码实现</button></li><li class="tab"><button type="button" data-href="#redis-distributed-lock-3">业务集成</button></li><li class="tab"><button type="button" data-href="#redis-distributed-lock-4">误删问题</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="redis-distributed-lock-1"><p><strong>Redis锁实现原理</strong></p><p><strong>获取锁</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 原子性操作：SETNX + EXPIRE</span></span><br><span class="line">SET lock_key unique_value NX EX 30</span><br></pre></td></tr></table></figure><p><strong>参数说明</strong>：</p><ul><li><code>NX</code>：key不存在时才设置（互斥性）</li><li><code>EX 30</code>：30秒自动过期（死锁避免）</li><li><code>unique_value</code>：线程唯一标识（安全性）</li></ul><p><strong>释放锁</strong>：</p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 原子性脚本：先验证再删除</span></span><br><span class="line"><span class="keyword">if</span> redis.call(<span class="string">&quot;get&quot;</span>, KEYS[<span class="number">1</span>]) == ARGV[<span class="number">1</span>] <span class="keyword">then</span></span><br><span class="line">    <span class="keyword">return</span> redis.call(<span class="string">&quot;del&quot;</span>, KEYS[<span class="number">1</span>])</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redis-distributed-lock-2"><p><strong>SimpleRedisLock实现</strong></p><p><strong>锁接口定义</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">ILock</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 尝试获取锁</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> timeoutSec 锁的超时时间（秒）</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> true-获取成功，false-获取失败</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">(<span class="type">long</span> timeoutSec)</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 释放锁</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>核心实现</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SimpleRedisLock</span> <span class="keyword">implements</span> <span class="title class_">ILock</span> &#123;</span><br><span class="line">    <span class="comment">//锁的前缀</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">KEY_PREFIX</span> <span class="operator">=</span> <span class="string">&quot;lock:&quot;</span>;</span><br><span class="line">    <span class="comment">//具体业务名称，将前缀和业务名拼接之后当做Key</span></span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="comment">//这里不是@Autowired注入，采用的是构造器注入，在创建SimpleRedisLock时，将RedisTemplate作为参数传入</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">SimpleRedisLock</span><span class="params">(String name, StringRedisTemplate stringRedisTemplate)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">        <span class="built_in">this</span>.stringRedisTemplate = stringRedisTemplate;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">(<span class="type">long</span> timeoutSec)</span> &#123;</span><br><span class="line">        <span class="comment">//获取线程标识</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">threadId</span> <span class="operator">=</span> Thread.currentThread().getId();</span><br><span class="line">        <span class="comment">//获取锁，使用SETNX方法进行加锁，同时设置过期时间，防止死锁</span></span><br><span class="line">        <span class="type">Boolean</span> <span class="variable">success</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId + <span class="string">&quot;&quot;</span>, timeoutSec, TimeUnit.SECONDS);</span><br><span class="line">        <span class="comment">//自动拆箱可能会出现null，这样写更稳妥</span></span><br><span class="line">        <span class="keyword">return</span> Boolean.TRUE.equals(success);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">//通过DEL来删除锁</span></span><br><span class="line">        stringRedisTemplate.delete(KEY_PREFIX + name);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redis-distributed-lock-3"><p><strong>秒杀业务改造</strong></p><p><strong>集成分布式锁</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">seckillVoucher</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line">    <span class="comment">// ... 前置校验代码 ...</span></span><br><span class="line">    </span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 创建分布式锁对象</span></span><br><span class="line">    <span class="type">SimpleRedisLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleRedisLock</span>(<span class="string">&quot;order:&quot;</span> + userId, stringRedisTemplate);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取锁</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> lock.tryLock(<span class="number">1200</span>);</span><br><span class="line">    <span class="keyword">if</span> (!isLock) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;不允许重复下单&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 获取代理对象（事务）</span></span><br><span class="line">        <span class="type">IVoucherOrderService</span> <span class="variable">proxy</span> <span class="operator">=</span> (IVoucherOrderService) AopContext.currentProxy();</span><br><span class="line">        <span class="keyword">return</span> proxy.createVoucherOrder(voucherId);</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="comment">// 释放锁</span></span><br><span class="line">        lock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>使用Jmeter进行压力测试，请求头中携带登录用户的token，最终只能抢到一张优惠券</strong></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redis-distributed-lock-4"><p><strong>锁误删问题分析</strong></p><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653385920025.png" alt="1653385920025"></p><div class="note warning flat"><p><strong>问题场景</strong>：线程阻塞导致锁超时，其他线程获取锁后，原线程恢复误删锁</p></div><p><strong>问题复现</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">时间线：</span><br><span class="line">t1: 线程A获取锁（锁超时30s）</span><br><span class="line">t2: 线程A业务阻塞（超过30s）</span><br><span class="line">t3: 锁自动过期释放</span><br><span class="line">t4: 线程B获取锁成功</span><br><span class="line">t5: 线程A恢复，执行删锁操作（误删线程B的锁）</span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：</p><ul><li>✅ <strong>唯一标识</strong>：每个线程使用不同的标识</li><li>✅ <strong>验证机制</strong>：删除前验证锁的持有者</li><li>✅ <strong>原子操作</strong>：使用Lua脚本保证验证+删除的原子性</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="4-3-分布式锁演进">4.3 分布式锁演进</h3><div class="tabs" id="distributed-lock-evolution"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#distributed-lock-evolution-1">基础版本</button></li><li class="tab"><button type="button" data-href="#distributed-lock-evolution-2">标识版本</button></li><li class="tab"><button type="button" data-href="#distributed-lock-evolution-3">Lua脚本版本</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="distributed-lock-evolution-1"><p><strong>版本一：基础实现</strong></p><div class="note primary flat"><p><strong>核心功能</strong>：SETNX + EXPIRE 原子操作</p></div><p><strong>问题</strong>：释放锁时可能误删其他线程的锁</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取锁（原子性）</span></span><br><span class="line"><span class="type">Boolean</span> <span class="variable">success</span> <span class="operator">=</span> stringRedisTemplate.opsForValue()</span><br><span class="line">    .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 释放锁（非原子性）</span></span><br><span class="line">stringRedisTemplate.delete(KEY_PREFIX + name);</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="distributed-lock-evolution-2"><p><strong>版本二：线程标识</strong></p><div class="note warning flat"><p><strong>改进</strong>：增加线程唯一标识，防止误删</p></div><p><strong>实现逻辑</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SimpleRedisLock</span> <span class="keyword">implements</span> <span class="title class_">ILock</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">KEY_PREFIX</span> <span class="operator">=</span> <span class="string">&quot;lock:&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">ID_PREFIX</span> <span class="operator">=</span> UUID.randomUUID().toString(<span class="literal">true</span>) + <span class="string">&quot;-&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">(<span class="type">long</span> timeoutSec)</span> &#123;</span><br><span class="line">        <span class="comment">// 获取线程唯一标识</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">threadId</span> <span class="operator">=</span> ID_PREFIX + Thread.currentThread().getId();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取锁（原子性操作）</span></span><br><span class="line">        <span class="type">Boolean</span> <span class="variable">success</span> <span class="operator">=</span> stringRedisTemplate.opsForValue()</span><br><span class="line">            .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> Boolean.TRUE.equals(success);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 获取线程标识</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">threadId</span> <span class="operator">=</span> ID_PREFIX + Thread.currentThread().getId();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取锁中的标识</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">id</span> <span class="operator">=</span> stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 验证是否为自己的锁</span></span><br><span class="line">        <span class="keyword">if</span> (threadId.equals(id)) &#123;</span><br><span class="line">            <span class="comment">// 释放锁</span></span><br><span class="line">            stringRedisTemplate.delete(KEY_PREFIX + name);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>新问题</strong>：<strong>验证和删除非原子性</strong>，仍存在竞态条件</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="distributed-lock-evolution-3"><p><strong>版本三：Lua脚本</strong></p><div class="note success flat"><p><strong>最终方案</strong>：Lua脚本保证<strong>验证+删除</strong>的原子性</p></div><p><strong>Lua脚本实现</strong>：</p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 释放锁脚本（unlock.lua）</span></span><br><span class="line"><span class="keyword">if</span> redis.call(<span class="string">&quot;get&quot;</span>, KEYS[<span class="number">1</span>]) == ARGV[<span class="number">1</span>] <span class="keyword">then</span></span><br><span class="line">    <span class="keyword">return</span> redis.call(<span class="string">&quot;del&quot;</span>, KEYS[<span class="number">1</span>])</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p><strong>Java调用</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> DefaultRedisScript&lt;Long&gt; UNLOCK_SCRIPT;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> &#123;</span><br><span class="line">    UNLOCK_SCRIPT = <span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;&gt;();</span><br><span class="line">    UNLOCK_SCRIPT.setLocation(<span class="keyword">new</span> <span class="title class_">ClassPathResource</span>(<span class="string">&quot;unlock.lua&quot;</span>));</span><br><span class="line">    UNLOCK_SCRIPT.setResultType(Long.class);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">()</span> &#123;</span><br><span class="line">    stringRedisTemplate.execute(</span><br><span class="line">        UNLOCK_SCRIPT,</span><br><span class="line">        Collections.singletonList(KEY_PREFIX + name),</span><br><span class="line">        ID_PREFIX + Thread.currentThread().getId()</span><br><span class="line">    );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="4-4-Lua脚本详解">4.4 Lua脚本详解</h3><div class='liushen-tag-link'><a class="tag-Link" target="_blank" href=" https://www.runoob.com/lua/lua-tutorial.html">    <div class="tag-link-tips">🪧引用站外地址，不保证站点的可用性和安全性</div>    <div class="tag-link-bottom">        <div class="tag-link-left" style="background-image: url(https://source.adoreorg.cn/webp/icon/66a4632bbf06e.webp);"></div>        <div class="tag-link-right">            <div class="tag-link-title">Lua脚本详解</div>            <div class="tag-link-sitename"> https://source.adoreorg.cn/webp/icon/66a4632bbf06e.webp</div>        </div>        <i class="fa-solid fa-angle-right"></i>    </div>    </a></div><div class="tabs" id="lua-script-detail"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#lua-script-detail-1">基础语法</button></li><li class="tab"><button type="button" data-href="#lua-script-detail-2">脚本调用</button></li><li class="tab"><button type="button" data-href="#lua-script-detail-3">原子性保证</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="lua-script-detail-1"><p><strong>Redis Lua脚本基础</strong></p><div class="note info flat"><p><strong>Lua脚本</strong>：在Redis服务器端执行的脚本，保证<strong>多条命令原子性</strong></p></div><p><strong>基本语法</strong>：</p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- Redis命令调用</span></span><br><span class="line">redis.call(<span class="string">&#x27;命令名称&#x27;</span>, <span class="string">&#x27;key&#x27;</span>, <span class="string">&#x27;其它参数&#x27;</span>, ...)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 示例：set name jack</span></span><br><span class="line">redis.call(<span class="string">&#x27;set&#x27;</span>, <span class="string">&#x27;name&#x27;</span>, <span class="string">&#x27;jack&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 示例：先set再get</span></span><br><span class="line">redis.call(<span class="string">&#x27;set&#x27;</span>, <span class="string">&#x27;name&#x27;</span>, <span class="string">&#x27;Rose&#x27;</span>)</span><br><span class="line"><span class="keyword">local</span> name = redis.call(<span class="string">&#x27;get&#x27;</span>, <span class="string">&#x27;name&#x27;</span>)</span><br><span class="line"><span class="keyword">return</span> name</span><br></pre></td></tr></table></figure><p><strong>参数传递</strong>：</p><ul><li><code>KEYS数组</code>：接收key类型参数</li><li><code>ARGV数组</code>：接收其他参数</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="lua-script-detail-2"><p><strong>Redis脚本调用</strong></p><p><strong>命令行调用</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 执行脚本</span></span><br><span class="line">EVAL <span class="string">&quot;redis.call(&#x27;set&#x27;, &#x27;name&#x27;, &#x27;jack&#x27;)&quot;</span> 0</span><br><span class="line"></span><br><span class="line"><span class="comment"># 带参数调用</span></span><br><span class="line">EVAL <span class="string">&quot;redis.call(&#x27;set&#x27;, KEYS[1], ARGV[1])&quot;</span> 1 name Rose</span><br></pre></td></tr></table></figure><p><strong>Java调用</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 脚本定义</span></span><br><span class="line">DefaultRedisScript&lt;Long&gt; script = <span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;&gt;();</span><br><span class="line">script.setScriptText(<span class="string">&quot;redis.call(&#x27;set&#x27;, KEYS[1], ARGV[1])&quot;</span>);</span><br><span class="line">script.setResultType(Long.class);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 执行脚本</span></span><br><span class="line">redisTemplate.execute(script, </span><br><span class="line">    Collections.singletonList(<span class="string">&quot;name&quot;</span>), </span><br><span class="line">    <span class="string">&quot;Jack&quot;</span>);</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="lua-script-detail-3"><p><strong>原子性保证机制</strong></p><div class="note success flat"><p><strong>Redis单线程模型</strong>：Lua脚本执行期间，Redis不会执行其他命令</p></div><p><strong>原子性验证</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">线程A：执行Lua脚本（验证+删除）</span><br><span class="line">Redis：脚本执行期间，线程B的请求排队等待</span><br><span class="line">结果：保证验证和删除操作的原子性</span><br></pre></td></tr></table></figure><p><strong>性能优势</strong>：</p><ul><li>✅ <strong>网络开销少</strong>：一次交互完成多个操作</li><li>✅ <strong>原子性保证</strong>：Redis单线程执行</li><li>✅ <strong>减少竞态</strong>：避免客户端并发问题</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="4-5-分布式锁总结">4.5 分布式锁总结</h3><div class="tabs" id="distributed-lock-summary"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#distributed-lock-summary-1">实现演进</button></li><li class="tab"><button type="button" data-href="#distributed-lock-summary-2">核心特性</button></li><li class="tab"><button type="button" data-href="#distributed-lock-summary-3">最佳实践</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="distributed-lock-summary-1"><p><strong>分布式锁演进历程</strong></p><div class="note primary flat"><p><strong>从简单到完善的演进过程</strong></p></div><table><thead><tr><th>版本</th><th>实现方式</th><th>解决问题</th><th>存在问题</th></tr></thead><tbody><tr><td><strong>V1</strong></td><td>SETNX + DEL</td><td>基本互斥</td><td>误删锁、死锁</td></tr><tr><td><strong>V2</strong></td><td>SET NX EX + 线程标识</td><td>死锁避免</td><td>误删问题</td></tr><tr><td><strong>V3</strong></td><td>线程标识 + 验证删除</td><td>防误删</td><td>原子性问题</td></tr><tr><td><strong>V4</strong></td><td>Lua脚本原子操作</td><td>原子性保证</td><td>功能单一</td></tr><tr><td><strong>V5</strong></td><td>Redisson框架</td><td>完整功能</td><td>依赖第三方</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="distributed-lock-summary-2"><p><strong>Redis分布式锁核心特性</strong></p><div class="note success flat"><p><strong>企业级实现要求</strong></p></div><p><strong>基本特性</strong>：</p><ul><li>✅ <strong>互斥性</strong>：SETNX保证同一时间只有一个客户端获取锁</li><li>✅ <strong>死锁避免</strong>：EXPIRE设置超时时间，防止死锁</li><li>✅ <strong>安全性</strong>：线程唯一标识，防止误删</li><li>✅ <strong>原子性</strong>：Lua脚本保证操作原子性</li></ul><p><strong>高级特性</strong>（Redisson提供）：</p><ul><li>🔄 <strong>可重入性</strong>：同一线程可重复获取锁</li><li>⏰ <strong>锁续期</strong>：WatchDog自动续期，防止业务未执行完锁过期</li><li>🔄 <strong>可重试</strong>：获取锁失败可自动重试</li><li>🏗️ <strong>主从一致性</strong>：RedLock算法保证主从一致性</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="distributed-lock-summary-3"><p><strong>使用建议</strong></p><div class="note warning flat"><p><strong>不同场景的选择策略</strong></p></div><p><strong>简单场景</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 简单分布式锁（适合低频操作）</span></span><br><span class="line"><span class="type">String</span> <span class="variable">lockKey</span> <span class="operator">=</span> <span class="string">&quot;lock:business:&quot;</span> + userId;</span><br><span class="line"><span class="type">String</span> <span class="variable">threadId</span> <span class="operator">=</span> UUID.randomUUID().toString();</span><br><span class="line"><span class="type">boolean</span> <span class="variable">locked</span> <span class="operator">=</span> redisTemplate.opsForValue()</span><br><span class="line">    .setIfAbsent(lockKey, threadId, <span class="number">30</span>, TimeUnit.SECONDS);</span><br></pre></td></tr></table></figure><p><strong>复杂场景</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Redisson（适合高频并发）</span></span><br><span class="line"><span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redissonClient.getLock(<span class="string">&quot;lock:order:&quot;</span> + userId);</span><br><span class="line"><span class="comment">// 可重入、可续期、可重试</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">locked</span> <span class="operator">=</span> lock.tryLock(<span class="number">1</span>, <span class="number">30</span>, TimeUnit.SECONDS);</span><br></pre></td></tr></table></figure><p><strong>选择原则</strong>：</p><ul><li><strong>低频简单操作</strong>：手写Redis分布式锁</li><li><strong>高频并发场景</strong>：Redisson框架</li><li><strong>强一致性要求</strong>：Zookeeper分布式锁</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="5-分布式锁-redission">5. 分布式锁-redission</h2><h3 id="5-1-Redisson概述">5.1 Redisson概述</h3><div class="tabs" id="redisson-overview"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#redisson-overview-1">基本介绍</button></li><li class="tab"><button type="button" data-href="#redisson-overview-2">问题解决</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="redisson-overview-1"><p><strong>什么是Redisson</strong></p><div class="note primary flat"><p><strong>Redisson</strong>：基于Redis的Java驻内存数据网格（In-Memory Data Grid）</p></div><p><strong>核心功能</strong>：</p><ul><li>🔒 <strong>分布式锁</strong>：可重入锁、公平锁、联锁、红锁等</li><li>📦 <strong>分布式对象</strong>：Object、List、Set、Map、Queue等</li><li>🔄 <strong>分布式服务</strong>：远程服务、消息服务、执行器服务等</li></ul><p><strong>优势特点</strong>：</p><ul><li>✅ <strong>功能完善</strong>：提供各种分布式锁实现</li><li>✅ <strong>可重入性</strong>：支持同一线程重复获取锁</li><li>✅ <strong>自动续期</strong>：WatchDog机制自动延长锁有效期</li><li>✅ <strong>高可用</strong>：支持主从、哨兵、集群模式</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redisson-overview-2"><p><strong>解决手写Redis锁的问题</strong></p><div class="note warning flat"><p><strong>手写Redis分布式锁的局限性</strong></p></div><table><thead><tr><th>问题</th><th>手写Redis锁</th><th>Redisson解决方案</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>WatchDog自动续期</td></tr><tr><td><strong>主从一致性</strong></td><td>主从切换可能丢锁</td><td>RedLock算法</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="5-2-Redisson快速入门">5.2 Redisson快速入门</h3><div class="tabs" id="redisson-quickstart"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#redisson-quickstart-1">依赖配置</button></li><li class="tab"><button type="button" data-href="#redisson-quickstart-2">客户端配置</button></li><li class="tab"><button type="button" data-href="#redisson-quickstart-3">基本使用</button></li><li class="tab"><button type="button" data-href="#redisson-quickstart-4">业务集成</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="redisson-quickstart-1"><p><strong>Maven依赖</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.redisson&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;redisson&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;<span class="number">3.13</span><span class="number">.6</span>&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redisson-quickstart-2"><p><strong>Redisson客户端配置</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedissonConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedissonClient <span class="title function_">redissonClient</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">// 配置</span></span><br><span class="line">        <span class="type">Config</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Config</span>();</span><br><span class="line">        config.useSingleServer()</span><br><span class="line">            .setAddress(<span class="string">&quot;redis://192.168.xxx.101:6379&quot;</span>)</span><br><span class="line">            .setPassword(<span class="string">&quot;123321&quot;</span>);</span><br><span class="line">        <span class="comment">// 创建RedissonClient对象</span></span><br><span class="line">        <span class="keyword">return</span> Redisson.create(config);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redisson-quickstart-3"><p><strong>分布式锁使用示例</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Resource</span></span><br><span class="line"><span class="keyword">private</span> RedissonClient redissonClient;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testRedisson</span><span class="params">()</span> <span class="keyword">throws</span> Exception&#123;</span><br><span class="line">    <span class="comment">// 获取锁(可重入)，指定锁的名称</span></span><br><span class="line">    <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redissonClient.getLock(<span class="string">&quot;anyLock&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 尝试获取锁，参数分别是：获取锁的最大等待时间(期间会重试)，锁自动释放时间，时间单位</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> lock.tryLock(<span class="number">1</span>, <span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 判断获取锁成功</span></span><br><span class="line">    <span class="keyword">if</span>(isLock)&#123;</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;执行业务&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">// 释放锁</span></span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redisson-quickstart-4"><p><strong>秒杀业务集成Redisson锁</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VoucherOrderServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">IVoucherOrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> RedissonClient redissonClient;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">seckillVoucher</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line">        <span class="comment">// 1.查询优惠券信息</span></span><br><span class="line">        <span class="type">SeckillVoucher</span> <span class="variable">voucher</span> <span class="operator">=</span> seckillVoucherService.getById(voucherId);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2.判断秒杀是否开始</span></span><br><span class="line">        <span class="keyword">if</span> (voucher.getBeginTime().isAfter(LocalDateTime.now())) &#123;</span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;秒杀尚未开始！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3.判断秒杀是否已经结束</span></span><br><span class="line">        <span class="keyword">if</span> (voucher.getEndTime().isBefore(LocalDateTime.now())) &#123;</span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;秒杀已经结束！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4.判断库存是否充足</span></span><br><span class="line">        <span class="keyword">if</span> (voucher.getStock() &lt; <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;库存不足！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 5.创建锁对象（使用Redisson分布式锁）</span></span><br><span class="line">        <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redissonClient.getLock(<span class="string">&quot;lock:order:&quot;</span> + userId);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 6.获取锁</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> lock.tryLock();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 7.判断是否获取锁成功</span></span><br><span class="line">        <span class="keyword">if</span> (!isLock) &#123;</span><br><span class="line">            <span class="keyword">return</span> Result.fail(<span class="string">&quot;不允许重复下单&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 8.获取代理对象（事务）</span></span><br><span class="line">            <span class="type">IVoucherOrderService</span> <span class="variable">proxy</span> <span class="operator">=</span> (IVoucherOrderService) AopContext.currentProxy();</span><br><span class="line">            <span class="keyword">return</span> proxy.createVoucherOrder(voucherId);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">// 9.释放锁</span></span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="5-3-Redisson分布式锁详解">5.3 Redisson分布式锁详解</h3><h4 id="5-3-1-Redisson可重入锁原理">5.3.1 Redisson可重入锁原理</h4><div class="tabs" id="redisson-reentrant"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#redisson-reentrant-1">可重入锁原理</button></li><li class="tab"><button type="button" data-href="#redisson-reentrant-2">数据结构</button></li><li class="tab"><button type="button" data-href="#redisson-reentrant-3">获取锁逻辑</button></li><li class="tab"><button type="button" data-href="#redisson-reentrant-4">释放锁逻辑</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="redisson-reentrant-1"><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Resource</span></span><br><span class="line"><span class="keyword">private</span> RedissonClient redissonClient;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> RLock lock;</span><br><span class="line"></span><br><span class="line"><span class="meta">@BeforeEach</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">setUp</span><span class="params">()</span> &#123;</span><br><span class="line">    lock = redissonClient.getLock(<span class="string">&quot;lock&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">method1</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> lock.tryLock();</span><br><span class="line">    <span class="keyword">if</span> (!success) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;获取锁失败，1&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;获取锁成功&quot;</span>);</span><br><span class="line">        method2();</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;释放锁，1&quot;</span>);</span><br><span class="line">        lock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">method2</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redissonClient.getLock(<span class="string">&quot;lock&quot;</span>);</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> lock.tryLock();</span><br><span class="line">    <span class="keyword">if</span> (!success) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;获取锁失败，2&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;获取锁成功，2&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;释放锁，2&quot;</span>);</span><br><span class="line">        lock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>同一线程内方法调用时，若method1已持有锁，method2需获取同一把锁，通过判断线程ID实现可重入：state+1获取锁，state-1释放锁，减至0时真正释放</strong></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redisson-reentrant-2"><p><strong>Redis中的锁存储结构</strong></p><div class="note primary flat"><p><strong>Hash结构存储可重入锁信息</strong></p></div><p><strong>存储格式</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Key: lock_name (锁名称)</span><br><span class="line">Value: Hash结构</span><br><span class="line">├─ field: UUID + &quot;:&quot; + threadId (线程唯一标识)</span><br><span class="line">└─ value: 重入次数 (整数)</span><br></pre></td></tr></table></figure><p><strong>示例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">lock:order:12345</span><br><span class="line">├─ &quot;8f3e2a1c:1&quot; : 2  (线程1重入了2次)</span><br><span class="line">└─ &quot;9d4c5b2a:2&quot; : 1  (线程2重入了1次)</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redisson-reentrant-3"><p><strong>可重入锁获取Lua脚本</strong></p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- KEYS[1]: 锁名称</span></span><br><span class="line"><span class="comment">-- ARGV[1]: 锁失效时间(毫秒)</span></span><br><span class="line"><span class="comment">-- ARGV[2]: 线程标识(UUID + &quot;:&quot; + threadId)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 锁不存在，创建新锁</span></span><br><span class="line"><span class="keyword">if</span> (redis.call(<span class="string">&#x27;exists&#x27;</span>, KEYS[<span class="number">1</span>]) == <span class="number">0</span>) <span class="keyword">then</span></span><br><span class="line">    redis.call(<span class="string">&#x27;hset&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>], <span class="number">1</span>);</span><br><span class="line">    redis.call(<span class="string">&#x27;pexpire&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">1</span>]);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 锁存在且是当前线程持有，重入次数+1</span></span><br><span class="line"><span class="keyword">if</span> (redis.call(<span class="string">&#x27;hexists&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>]) == <span class="number">1</span>) <span class="keyword">then</span></span><br><span class="line">    redis.call(<span class="string">&#x27;hincrby&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>], <span class="number">1</span>);</span><br><span class="line">    redis.call(<span class="string">&#x27;pexpire&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">1</span>]);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 锁存在但不是当前线程持有，返回锁剩余时间</span></span><br><span class="line"><span class="keyword">return</span> redis.call(<span class="string">&#x27;pttl&#x27;</span>, KEYS[<span class="number">1</span>]);</span><br></pre></td></tr></table></figure><p><strong>脚本逻辑</strong>：</p><ol><li><strong>锁不存在</strong>：创建新锁，重入次数设为1</li><li><strong>当前线程重入</strong>：重入次数+1，重置过期时间</li><li><strong>其他线程持有</strong>：返回锁剩余时间，抢锁失败</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redisson-reentrant-4"><p><strong>可重入锁释放Lua脚本</strong></p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- KEYS[1]: 锁名称</span></span><br><span class="line"><span class="comment">-- ARGV[1]: 线程标识</span></span><br><span class="line"><span class="comment">-- ARGV[2]: 锁失效时间</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 锁不存在，直接返回</span></span><br><span class="line"><span class="keyword">if</span> (redis.call(<span class="string">&#x27;hexists&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">1</span>]) == <span class="number">0</span>) <span class="keyword">then</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 重入次数-1</span></span><br><span class="line">counter = redis.call(<span class="string">&#x27;hincrby&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">1</span>], <span class="number">-1</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 重入次数为0，删除锁</span></span><br><span class="line"><span class="keyword">if</span> (counter &gt; <span class="number">0</span>) <span class="keyword">then</span></span><br><span class="line">    redis.call(<span class="string">&#x27;pexpire&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>]);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    redis.call(<span class="string">&#x27;del&#x27;</span>, KEYS[<span class="number">1</span>]);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">end</span>;</span><br></pre></td></tr></table></figure><p><strong>释放逻辑</strong>：</p><ul><li><strong>重入次数&gt;0</strong>：仅减少重入次数，不删除锁</li><li><strong>重入次数=0</strong>：删除整个锁</li><li><strong>锁不存在</strong>：直接返回，防止误删</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h4 id="5-3-2-Redisson锁重试和WatchDog机制">5.3.2 Redisson锁重试和WatchDog机制</h4><div class="tabs" id="redisson-watchdog"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#redisson-watchdog-1">重试机制</button></li><li class="tab"><button type="button" data-href="#redisson-watchdog-2">WatchDog机制</button></li><li class="tab"><button type="button" data-href="#redisson-watchdog-3">使用对比</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="redisson-watchdog-1"><p><strong>锁获取重试机制</strong></p><div class="note primary flat"><p><strong>tryLock方法的重试逻辑</strong></p></div><p><strong>重试流程</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 尝试获取锁，最多等待1秒，锁有效期10秒</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> lock.tryLock(<span class="number">1</span>, <span class="number">10</span>, TimeUnit.SECONDS);</span><br></pre></td></tr></table></figure><p><strong>内部实现</strong>：</p><ol><li><strong>首次尝试</strong>：立即执行Lua脚本抢锁</li><li><strong>失败重试</strong>：如果锁被占用，等待锁释放后重试</li><li><strong>超时控制</strong>：在指定等待时间内持续重试</li><li><strong>返回结果</strong>：成功返回true，超时返回false</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redisson-watchdog-2"><p><strong>看门狗自动续期机制</strong></p><div class="note warning flat"><p><strong>解决业务执行时间超过锁有效期的问题</strong></p></div><p><strong>续期原理</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// lock()方法默认开启WatchDog</span></span><br><span class="line">lock.lock(); <span class="comment">// 30秒有效期，WatchDog每10秒续期一次</span></span><br></pre></td></tr></table></figure><p><strong>续期流程</strong>：</p><ol><li><strong>初始有效期</strong>：默认30秒（可配置）</li><li><strong>续期触发</strong>：每<code>有效期/3</code>时间触发一次（默认10秒）</li><li><strong>续期条件</strong>：业务线程仍在运行且持有锁</li><li><strong>续期失败</strong>：线程宕机或锁已释放，停止续期</li></ol><p><strong>代码实现</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">renewExpiration</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 从续约映射中获取当前线程的续约条目</span></span><br><span class="line">    <span class="type">ExpirationEntry</span> <span class="variable">ee</span> <span class="operator">=</span> EXPIRATION_RENEWAL_MAP.get(getEntryName());</span><br><span class="line">    <span class="keyword">if</span> (ee == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 创建定时任务，internalLockLeaseTime/3后执行</span></span><br><span class="line">    <span class="type">Timeout</span> <span class="variable">task</span> <span class="operator">=</span> commandExecutor.getConnectionManager().newTimeout(</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">TimerTask</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">(Timeout timeout)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                <span class="comment">// 异步续期锁有效期</span></span><br><span class="line">                RFuture&lt;Boolean&gt; future = renewExpirationAsync(threadId);</span><br><span class="line">                future.onComplete((res, e) -&gt; &#123;</span><br><span class="line">                    <span class="keyword">if</span> (res) &#123;</span><br><span class="line">                        <span class="comment">// 续期成功，递归调用继续下一轮续期</span></span><br><span class="line">                        renewExpiration();</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;, </span><br><span class="line">        internalLockLeaseTime / <span class="number">3</span>,  <span class="comment">// 默认10秒</span></span><br><span class="line">        TimeUnit.MILLISECONDS</span><br><span class="line">    );</span><br><span class="line">    </span><br><span class="line">    ee.setTimeout(task);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redisson-watchdog-3"><p><strong>不同加锁方式的对比</strong></p><table><thead><tr><th>方法</th><th>是否重试</th><th>是否续期</th><th>适用场景</th></tr></thead><tbody><tr><td><code>tryLock()</code></td><td>❌</td><td>❌</td><td>简单获取，立即返回</td></tr><tr><td><code>tryLock(waitTime, leaseTime, unit)</code></td><td>✅</td><td>❌</td><td>带超时等待，固定有效期</td></tr><tr><td><code>lock()</code></td><td>✅</td><td>✅</td><td>长期持有，自动续期</td></tr><tr><td><code>lock(leaseTime, unit)</code></td><td>✅</td><td>❌</td><td>固定有效期，不续期</td></tr></tbody></table><p><strong>最佳实践</strong>：</p><ul><li><strong>短时操作</strong>：<code>tryLock(1, 10, TimeUnit.SECONDS)</code></li><li><strong>长时操作</strong>：<code>lock()</code> + WatchDog续期</li><li><strong>定时任务</strong>：<code>lock(30, TimeUnit.SECONDS)</code> 手动控制有效期</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/635d046816f2c2beb1293315.jpg" alt="Redisson MultiLock原理"></p><h4 id="5-3-3-Redisson-MultiLock原理">5.3.3 Redisson MultiLock原理</h4><div class="tabs" id="redisson-multilock"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#redisson-multilock-1">问题背景</button></li><li class="tab"><button type="button" data-href="#redisson-multilock-2">实现原理</button></li><li class="tab"><button type="button" data-href="#redisson-multilock-3">优缺点</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="redisson-multilock-1"><p><strong>Redis主从架构中的分布式锁失效风险分析</strong></p><div class="note danger flat"><p><strong>主从切换导致的锁信息丢失</strong></p></div><p><strong>问题场景</strong>：</p><ol><li><strong>主节点写入锁</strong>：客户端在Master节点成功获取锁</li><li><strong>主节点宕机</strong>：数据还未同步到Slave节点</li><li><strong>主从切换</strong>：Slave升级为新的Master</li><li><strong>锁信息丢失</strong>：新Master没有锁信息，其他客户端可重新获取锁</li></ol><p><strong>解决方案</strong>：MultiLock机制，在多个独立Redis实例上同时加锁</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redisson-multilock-2"><p><strong>MultiLock核心原理</strong></p><div class="note primary flat"><p><strong>多数派原则保证锁可靠性</strong></p></div><p><strong>加锁规则</strong>：</p><ul><li><strong>全部成功</strong>：在所有节点都成功加锁才算成功</li><li><strong>超时控制</strong>：总超时时间 = 节点数量 × 1500ms</li><li><strong>失败处理</strong>：任意节点失败则整个加锁失败</li></ul><p><strong>示例代码</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="comment">// 配置多个Redis实例</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedissonConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedissonClient <span class="title function_">redissonClient</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">Config</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Config</span>();</span><br><span class="line">        config.useSingleServer().setAddress(<span class="string">&quot;redis://192.168.137.130:6379&quot;</span>)</span><br><span class="line">                .setPassword(<span class="string">&quot;root&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> Redisson.create(config);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedissonClient <span class="title function_">redissonClient2</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">Config</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Config</span>();</span><br><span class="line">        config.useSingleServer().setAddress(<span class="string">&quot;redis://92.168.137.131:6379&quot;</span>)</span><br><span class="line">                .setPassword(<span class="string">&quot;root&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> Redisson.create(config);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedissonClient <span class="title function_">redissonClient3</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">Config</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Config</span>();</span><br><span class="line">        config.useSingleServer().setAddress(<span class="string">&quot;redis://92.168.137.132:6379&quot;</span>)</span><br><span class="line">                .setPassword(<span class="string">&quot;root&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> Redisson.create(config);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Resource</span></span><br><span class="line"><span class="keyword">private</span> RedissonClient redissonClient;</span><br><span class="line"><span class="meta">@Resource</span></span><br><span class="line"><span class="keyword">private</span> RedissonClient redissonClient2;</span><br><span class="line"><span class="meta">@Resource</span></span><br><span class="line"><span class="keyword">private</span> RedissonClient redissonClient3;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> RLock lock;</span><br><span class="line"></span><br><span class="line"><span class="meta">@BeforeEach</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">setUp</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">RLock</span> <span class="variable">lock1</span> <span class="operator">=</span> redissonClient.getLock(<span class="string">&quot;lock&quot;</span>);</span><br><span class="line">    <span class="type">RLock</span> <span class="variable">lock2</span> <span class="operator">=</span> redissonClient2.getLock(<span class="string">&quot;lock&quot;</span>);</span><br><span class="line">    <span class="type">RLock</span> <span class="variable">lock3</span> <span class="operator">=</span> redissonClient3.getLock(<span class="string">&quot;lock&quot;</span>);</span><br><span class="line">    lock = redissonClient.getMultiLock(lock1, lock2, lock3);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">method1</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> lock.tryLock();</span><br><span class="line">    redissonClient.getMultiLock();</span><br><span class="line">    <span class="keyword">if</span> (!success) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;获取锁失败，1&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;获取锁成功&quot;</span>);</span><br><span class="line">        method2();</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;释放锁，1&quot;</span>);</span><br><span class="line">        lock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">method2</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redissonClient.getLock(<span class="string">&quot;lock&quot;</span>);</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> lock.tryLock();</span><br><span class="line">    <span class="keyword">if</span> (!success) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;获取锁失败，2&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;获取锁成功，2&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;释放锁，2&quot;</span>);</span><br><span class="line">        lock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redisson-multilock-3"><p><strong>MultiLock优缺点分析</strong></p><table><thead><tr><th>特性</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td><strong>可靠性</strong></td><td>避免单点故障，主从切换不丢锁</td><td>需要维护多个独立Redis实例</td></tr><tr><td><strong>性能</strong></td><td>并行加锁，性能可接受</td><td>网络开销增加，延迟上升</td></tr><tr><td><strong>复杂度</strong></td><td>使用简单，API统一</td><td>需要额外的Redis实例资源</td></tr><tr><td><strong>一致性</strong></td><td>基于多数派，一致性强</td><td>网络分区时可能出现不可用</td></tr></tbody></table><p><strong>适用场景</strong>：</p><ul><li>✅ <strong>高可靠性要求</strong>：金融交易、订单处理等关键业务</li><li>✅ <strong>主从架构</strong>：Redis主从部署环境</li><li>❌ <strong>简单场景</strong>：单机Redis即可满足需求</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="6-秒杀优化">6. 秒杀优化</h2><h3 id="6-1-异步秒杀思路">6.1 异步秒杀思路</h3><div class="tabs" id="async-seckill"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#async-seckill-1">问题分析</button></li><li class="tab"><button type="button" data-href="#async-seckill-2">优化方案</button></li><li class="tab"><button type="button" data-href="#async-seckill-3">实现难点</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="async-seckill-1"><p><strong>同步秒杀的性能瓶颈</strong></p><div class="note warning flat"><p><strong>串行操作导致的性能问题</strong></p></div><p><strong>传统流程</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">查询优惠券 → 判断库存 → 查询订单 → 一人一单校验 → 扣减库存 → 创建订单</span><br></pre></td></tr></table></figure><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653560986599.png" alt="1653560986599"></p><p><strong>性能问题</strong>：</p><ul><li><strong>数据库压力大</strong>：每个请求都要多次访问数据库</li><li><strong>串行执行</strong>：所有操作顺序执行，耗时累积</li><li><strong>线程阻塞</strong>：数据库IO等待导致线程闲置</li><li><strong>并发能力低</strong>：QPS受限于数据库性能</li></ul><p><strong>优化思路</strong>：将耗时短的逻辑判断移到Redis，耗时长的下单操作异步化</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="async-seckill-2"><p><strong>异步化优化方案</strong></p><div class="note success flat"><p><strong>Redis缓存 + 消息队列异步处理</strong></p></div><p><strong>优化流程</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">graph TD</span><br><span class="line">    A[用户请求] --&gt; B[Redis校验]</span><br><span class="line">    B --&gt; C&#123;校验通过?&#125;</span><br><span class="line">    C --&gt;|是| D[返回成功]</span><br><span class="line">    C --&gt;|否| E[返回失败]</span><br><span class="line">    D --&gt; F[消息队列]</span><br><span class="line">    F --&gt; G[异步下单]</span><br><span class="line">    G --&gt; H[数据库更新]</span><br></pre></td></tr></table></figure><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653561657295.png" alt="1653561657295"></p><p><strong>核心思想</strong>：</p><ul><li><strong>快速响应</strong>：Redis内存操作，毫秒级响应</li><li><strong>异步处理</strong>：消息队列削峰填谷，平滑处理</li><li><strong>最终一致性</strong>：保证订单最终创建成功</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="async-seckill-3"><p><strong>异步化实现难点</strong></p><div class="note info flat"><p><strong>需要解决的关键问题</strong></p></div><p><strong>技术难点</strong>：</p><ol><li><strong>Redis原子操作</strong>：如何保证库存扣减和订单记录的原子性？</li><li><strong>一人一单校验</strong>：Redis中如何快速判断用户是否已下单？</li><li><strong>订单状态跟踪</strong>：如何告知用户订单处理结果？</li><li><strong>消息可靠性</strong>：如何保证消息不丢失，正确处理？</li></ol><p><strong>解决方案</strong>：</p><ul><li><strong>Lua脚本</strong>：保证Redis操作的原子性</li><li><strong>Set集合</strong>：使用Redis Set存储已下单用户ID</li><li><strong>订单ID预生成</strong>：提前生成订单ID，用于状态查询</li><li><strong>消息队列</strong>：使用Redis Stream或专业消息队列</li></ul><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653562234886.png" alt="1653562234886"></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="6-2-Redis完成秒杀资格判断">6.2 Redis完成秒杀资格判断</h3><div class="tabs" id="redis-qualification"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#redis-qualification-1">实现思路</button></li><li class="tab"><button type="button" data-href="#redis-qualification-2">代码实现</button></li><li class="tab"><button type="button" data-href="#redis-qualification-3">关键点</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="redis-qualification-1"><p><strong>Redis原子操作实现资格判断</strong></p><div class="note success flat"><p><strong>Lua脚本保证原子性</strong></p></div><p><strong>核心思路</strong>：</p><ol><li><strong>库存预加载</strong>：优惠券信息保存到Redis</li><li><strong>原子校验</strong>：Lua脚本一次性完成库存、一人一单判断</li><li><strong>异步下单</strong>：校验通过后发送消息到队列</li></ol><p><strong>实现步骤</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Redis预加载 → Lua脚本校验 → 消息队列 → 异步下单</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redis-qualification-2"><p><strong>优惠券预加载</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addSeckillVoucher</span><span class="params">(Voucher voucher)</span> &#123;</span><br><span class="line">    <span class="comment">// 保存优惠券</span></span><br><span class="line">    save(voucher);</span><br><span class="line">    <span class="comment">// 保存秒杀信息</span></span><br><span class="line">    <span class="type">SeckillVoucher</span> <span class="variable">seckillVoucher</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SeckillVoucher</span>();</span><br><span class="line">    seckillVoucher.setVoucherId(voucher.getId());</span><br><span class="line">    seckillVoucher.setStock(voucher.getStock());</span><br><span class="line">    seckillVoucher.setBeginTime(voucher.getBeginTime());</span><br><span class="line">    seckillVoucher.setEndTime(voucher.getEndTime());</span><br><span class="line">    seckillVoucherService.save(seckillVoucher);</span><br><span class="line">    <span class="comment">// 保存秒杀库存到Redis中</span></span><br><span class="line">    stringRedisTemplate.opsForValue().set(SECKILL_STOCK_KEY + voucher.getId(), </span><br><span class="line">                                         voucher.getStock().toString());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Lua脚本实现</strong></p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 1.参数列表</span></span><br><span class="line"><span class="keyword">local</span> voucherId = ARGV[<span class="number">1</span>]      <span class="comment">-- 优惠券id</span></span><br><span class="line"><span class="keyword">local</span> userId = ARGV[<span class="number">2</span>]       <span class="comment">-- 用户id  </span></span><br><span class="line"><span class="keyword">local</span> orderId = ARGV[<span class="number">3</span>]       <span class="comment">-- 订单id</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 2.数据key</span></span><br><span class="line"><span class="keyword">local</span> stockKey = <span class="string">&#x27;seckill:stock:&#x27;</span> .. voucherId    <span class="comment">-- 库存key</span></span><br><span class="line"><span class="keyword">local</span> orderKey = <span class="string">&#x27;seckill:order:&#x27;</span> .. voucherId     <span class="comment">-- 订单key</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 3.脚本业务</span></span><br><span class="line"><span class="comment">-- 3.1.判断库存是否充足</span></span><br><span class="line"><span class="keyword">if</span>(<span class="built_in">tonumber</span>(redis.call(<span class="string">&#x27;get&#x27;</span>, stockKey)) &lt;= <span class="number">0</span>) <span class="keyword">then</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>  <span class="comment">-- 库存不足</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 3.2.判断用户是否已下单</span></span><br><span class="line"><span class="keyword">if</span>(redis.call(<span class="string">&#x27;sismember&#x27;</span>, orderKey, userId) == <span class="number">1</span>) <span class="keyword">then</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">2</span>  <span class="comment">-- 重复下单</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 3.3.扣减库存</span></span><br><span class="line">redis.call(<span class="string">&#x27;incrby&#x27;</span>, stockKey, <span class="number">-1</span>)</span><br><span class="line"><span class="comment">-- 3.4.记录用户已下单</span></span><br><span class="line">redis.call(<span class="string">&#x27;sadd&#x27;</span>, orderKey, userId)</span><br><span class="line"><span class="comment">-- 3.5.发送消息到队列</span></span><br><span class="line">redis.call(<span class="string">&#x27;xadd&#x27;</span>, <span class="string">&#x27;stream.orders&#x27;</span>, <span class="string">&#x27;*&#x27;</span>, </span><br><span class="line">          <span class="string">&#x27;userId&#x27;</span>, userId, <span class="string">&#x27;voucherId&#x27;</span>, voucherId, <span class="string">&#x27;id&#x27;</span>, orderId)</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>  <span class="comment">-- 抢购成功</span></span><br></pre></td></tr></table></figure><p><strong>业务层调用</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">seckillVoucher</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="type">long</span> <span class="variable">orderId</span> <span class="operator">=</span> redisIdWorker.nextId(<span class="string">&quot;order&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 执行lua脚本</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">result</span> <span class="operator">=</span> stringRedisTemplate.execute(</span><br><span class="line">        SECKILL_SCRIPT,</span><br><span class="line">        Collections.emptyList(),</span><br><span class="line">        voucherId.toString(),</span><br><span class="line">        userId.toString(), </span><br><span class="line">        String.valueOf(orderId)</span><br><span class="line">    );</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 判断结果</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">r</span> <span class="operator">=</span> result.intValue();</span><br><span class="line">    <span class="keyword">if</span> (r != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(r == <span class="number">1</span> ? <span class="string">&quot;库存不足！&quot;</span> : <span class="string">&quot;不能重复下单！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> Result.ok(orderId);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>使用PostMan发送请求，添加优惠券</strong><br><strong>请求路径：<a href="http://localhost:8080/api/voucher/seckill">http://localhost:8080/api/voucher/seckill</a></strong><br><strong>请求方式：POST</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;shopId&quot;</span><span class="punctuation">:</span><span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span><span class="string">&quot;9999元代金券&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;subTitle&quot;</span><span class="punctuation">:</span><span class="string">&quot;365*24小时可用&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;rules&quot;</span><span class="punctuation">:</span><span class="string">&quot;全场通用\\nApex猎杀无需预约&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;payValue&quot;</span><span class="punctuation">:</span><span class="number">1000</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;actualValue&quot;</span><span class="punctuation">:</span><span class="number">999900</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span><span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;stock&quot;</span><span class="punctuation">:</span><span class="number">100</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;beginTime&quot;</span><span class="punctuation">:</span><span class="string">&quot;2022-01-01T00:00:00&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;endTime&quot;</span><span class="punctuation">:</span><span class="string">&quot;2022-12-31T23:59:59&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="redis-qualification-3"><p><strong>实现关键点</strong></p><div class="note warning flat"><p><strong>需要注意的细节问题</strong></p></div><p><strong>原子性保证</strong>：</p><ul><li>✅ <strong>Lua脚本</strong>：所有Redis操作一次性执行</li><li>✅ <strong>单线程模型</strong>：Redis单线程保证脚本执行不被打断</li><li>❌ <strong>事务</strong>：MULTI/EXEC无法保证库存和订单的原子性</li></ul><p><strong>数据一致性</strong>：</p><ul><li><strong>库存Key</strong>：<code>seckill:stock:{voucherId}</code></li><li><strong>订单Key</strong>：<code>seckill:order:{voucherId}</code></li><li><strong>消息队列</strong>：<code>stream.orders</code></li></ul><p><strong>返回值设计</strong>：</p><ul><li><code>0</code>：抢购成功</li><li><code>1</code>：库存不足</li><li><code>2</code>：重复下单</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="6-3-基于阻塞队列实现秒杀优化">6.3 基于阻塞队列实现秒杀优化</h3><div class="tabs" id="blocking-queue"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#blocking-queue-1">实现思路</button></li><li class="tab"><button type="button" data-href="#blocking-queue-2">代码实现</button></li><li class="tab"><button type="button" data-href="#blocking-queue-3">完整代码</button></li><li class="tab"><button type="button" data-href="#blocking-queue-4">优缺点</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="blocking-queue-1"><p><strong>阻塞队列异步处理方案</strong></p><div class="note info flat"><p><strong>内存队列 + 单线程异步处理</strong></p></div><p><strong>核心思路</strong>：</p><ol><li><strong>快速响应</strong>：Lua脚本校验通过后立即返回订单ID</li><li><strong>异步处理</strong>：订单信息放入阻塞队列，后台线程慢慢处理</li><li><strong>流量削峰</strong>：阻塞队列缓冲瞬时高并发请求</li></ol><p><strong>实现架构</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">用户请求 → Lua脚本校验 → 内存队列 → 单线程处理 → 数据库操作</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="blocking-queue-2"><p><strong>线程池配置</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 异步处理线程池</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">ExecutorService</span> <span class="variable">SECKILL_ORDER_EXECUTOR</span> <span class="operator">=</span> </span><br><span class="line">    Executors.newSingleThreadExecutor();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 类初始化后立即执行</span></span><br><span class="line"><span class="meta">@PostConstruct</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">    SECKILL_ORDER_EXECUTOR.submit(<span class="keyword">new</span> <span class="title class_">VoucherOrderHandler</span>());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>订单处理器</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">class</span> <span class="title class_">VoucherOrderHandler</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 1.获取队列中的订单信息</span></span><br><span class="line">                <span class="type">VoucherOrder</span> <span class="variable">voucherOrder</span> <span class="operator">=</span> orderTasks.take();</span><br><span class="line">                <span class="comment">// 2.处理订单</span></span><br><span class="line">                handleVoucherOrder(voucherOrder);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                log.error(<span class="string">&quot;处理订单异常&quot;</span>, e);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">handleVoucherOrder</span><span class="params">(VoucherOrder voucherOrder)</span> &#123;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> voucherOrder.getUserId();</span><br><span class="line">        <span class="comment">// 创建锁对象，防止重复下单</span></span><br><span class="line">        <span class="type">RLock</span> <span class="variable">redisLock</span> <span class="operator">=</span> redissonClient.getLock(<span class="string">&quot;lock:order:&quot;</span> + userId);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 尝试获取锁</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> redisLock.lock();</span><br><span class="line">        <span class="keyword">if</span> (!isLock) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;不允许重复下单！&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 注意：Spring事务在ThreadLocal中，多线程环境下会失效</span></span><br><span class="line">            proxy.createVoucherOrder(voucherOrder);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">// 释放锁</span></span><br><span class="line">            redisLock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span>  <span class="keyword">void</span> <span class="title function_">createVoucherOrder</span><span class="params">(VoucherOrder voucherOrder)</span> &#123;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> voucherOrder.getUserId();</span><br><span class="line">        <span class="comment">// 5.1.查询订单</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> query().eq(<span class="string">&quot;user_id&quot;</span>, userId).eq(<span class="string">&quot;voucher_id&quot;</span>, voucherOrder.getVoucherId()).count();</span><br><span class="line">        <span class="comment">// 5.2.判断是否存在</span></span><br><span class="line">        <span class="keyword">if</span> (count &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 用户已经购买过了</span></span><br><span class="line">           log.error(<span class="string">&quot;用户已经购买过了&quot;</span>);</span><br><span class="line">           <span class="keyword">return</span> ;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 6.扣减库存</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> seckillVoucherService.update()</span><br><span class="line">                .setSql(<span class="string">&quot;stock = stock - 1&quot;</span>) <span class="comment">// set stock = stock - 1</span></span><br><span class="line">                .eq(<span class="string">&quot;voucher_id&quot;</span>, voucherOrder.getVoucherId()).gt(<span class="string">&quot;stock&quot;</span>, <span class="number">0</span>) <span class="comment">// where id = ? and stock &gt; 0</span></span><br><span class="line">                .update();</span><br><span class="line">        <span class="keyword">if</span> (!success) &#123;</span><br><span class="line">            <span class="comment">// 扣减失败</span></span><br><span class="line">            log.error(<span class="string">&quot;库存不足&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> ;</span><br><span class="line">        &#125;</span><br><span class="line">        save(voucherOrder);</span><br><span class="line"> </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>阻塞队列定义</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 订单阻塞队列，容量1024*1024</span></span><br><span class="line"><span class="keyword">private</span> BlockingQueue&lt;VoucherOrder&gt; orderTasks = </span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">ArrayBlockingQueue</span>&lt;&gt;(<span class="number">1024</span> * <span class="number">1024</span>);</span><br></pre></td></tr></table></figure><p><strong>业务层修改</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">seckillVoucher</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="type">long</span> <span class="variable">orderId</span> <span class="operator">=</span> redisIdWorker.nextId(<span class="string">&quot;order&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1.执行lua脚本</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">result</span> <span class="operator">=</span> stringRedisTemplate.execute(</span><br><span class="line">        SECKILL_SCRIPT,</span><br><span class="line">        Collections.emptyList(),</span><br><span class="line">        voucherId.toString(), userId.toString(), String.valueOf(orderId)</span><br><span class="line">    );</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2.判断结果</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">r</span> <span class="operator">=</span> result.intValue();</span><br><span class="line">    <span class="keyword">if</span> (r != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(r == <span class="number">1</span> ? <span class="string">&quot;库存不足&quot;</span> : <span class="string">&quot;不能重复下单&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3.创建订单对象</span></span><br><span class="line">    <span class="type">VoucherOrder</span> <span class="variable">voucherOrder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">VoucherOrder</span>();</span><br><span class="line">    voucherOrder.setId(orderId);</span><br><span class="line">    voucherOrder.setUserId(userId);</span><br><span class="line">    voucherOrder.setVoucherId(voucherId);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4.放入阻塞队列</span></span><br><span class="line">    orderTasks.add(voucherOrder);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 5.返回订单id</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok(orderId);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="blocking-queue-3"><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.hmdp.service.impl;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.hmdp.dto.Result;</span><br><span class="line"><span class="keyword">import</span> com.hmdp.entity.VoucherOrder;</span><br><span class="line"><span class="keyword">import</span> com.hmdp.mapper.VoucherOrderMapper;</span><br><span class="line"><span class="keyword">import</span> com.hmdp.service.ISeckillVoucherService;</span><br><span class="line"><span class="keyword">import</span> com.hmdp.service.IVoucherOrderService;</span><br><span class="line"><span class="keyword">import</span> com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;</span><br><span class="line"><span class="keyword">import</span> com.hmdp.utils.RedisIdWorker;</span><br><span class="line"><span class="keyword">import</span> com.hmdp.utils.UserHolder;</span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"><span class="keyword">import</span> org.redisson.api.RLock;</span><br><span class="line"><span class="keyword">import</span> org.redisson.api.RedissonClient;</span><br><span class="line"><span class="keyword">import</span> org.springframework.aop.framework.AopContext;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.core.io.ClassPathResource;</span><br><span class="line"><span class="keyword">import</span> org.springframework.data.redis.core.StringRedisTemplate;</span><br><span class="line"><span class="keyword">import</span> org.springframework.data.redis.core.script.DefaultRedisScript;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Service;</span><br><span class="line"><span class="keyword">import</span> org.springframework.transaction.annotation.Transactional;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.annotation.PostConstruct;</span><br><span class="line"><span class="keyword">import</span> javax.annotation.Resource;</span><br><span class="line"><span class="keyword">import</span> java.util.Collections;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.ArrayBlockingQueue;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.BlockingQueue;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.ExecutorService;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.Executors;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * &lt;p&gt;</span></span><br><span class="line"><span class="comment"> * 服务实现类</span></span><br><span class="line"><span class="comment"> * &lt;/p&gt;</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Kyle</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> 2022-10-22</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VoucherOrderServiceImpl</span> <span class="keyword">extends</span> <span class="title class_">ServiceImpl</span>&lt;VoucherOrderMapper, VoucherOrder&gt; <span class="keyword">implements</span> <span class="title class_">IVoucherOrderService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ISeckillVoucherService seckillVoucherService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedisIdWorker redisIdWorker;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> RedissonClient redissonClient;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> IVoucherOrderService proxy;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> DefaultRedisScript&lt;Long&gt; SECKILL_SCRIPT;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        SECKILL_SCRIPT = <span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>();</span><br><span class="line">        SECKILL_SCRIPT.setLocation(<span class="keyword">new</span> <span class="title class_">ClassPathResource</span>(<span class="string">&quot;seckill.lua&quot;</span>));</span><br><span class="line">        SECKILL_SCRIPT.setResultType(Long.class);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">ExecutorService</span> <span class="variable">SECKILL_ORDER_EXECUTOR</span> <span class="operator">=</span> Executors.newSingleThreadExecutor();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">        SECKILL_ORDER_EXECUTOR.submit(<span class="keyword">new</span> <span class="title class_">VoucherOrderHandler</span>());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> BlockingQueue&lt;VoucherOrder&gt; orderTasks = <span class="keyword">new</span> <span class="title class_">ArrayBlockingQueue</span>&lt;&gt;(<span class="number">1024</span> * <span class="number">1024</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">handleVoucherOrder</span><span class="params">(VoucherOrder voucherOrder)</span> &#123;</span><br><span class="line">        <span class="comment">//1. 获取用户</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> voucherOrder.getUserId();</span><br><span class="line">        <span class="comment">//2. 创建锁对象，作为兜底方案</span></span><br><span class="line">        <span class="type">RLock</span> <span class="variable">redisLock</span> <span class="operator">=</span> redissonClient.getLock(<span class="string">&quot;order:&quot;</span> + userId);</span><br><span class="line">        <span class="comment">//3. 获取锁</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">isLock</span> <span class="operator">=</span> redisLock.tryLock();</span><br><span class="line">        <span class="comment">//4. 判断是否获取锁成功 </span></span><br><span class="line">        <span class="keyword">if</span> (!isLock) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;不允许重复下单!&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//5. 使用代理对象，由于这里是另外一个线程，</span></span><br><span class="line">            proxy.createVoucherOrder(voucherOrder);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            redisLock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">class</span> <span class="title class_">VoucherOrderHandler</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="comment">//1. 获取队列中的订单信息</span></span><br><span class="line">                    <span class="type">VoucherOrder</span> <span class="variable">voucherOrder</span> <span class="operator">=</span> orderTasks.take();</span><br><span class="line">                    <span class="comment">//2. 创建订单</span></span><br><span class="line">                    handleVoucherOrder(voucherOrder);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                    log.error(<span class="string">&quot;订单处理异常&quot;</span>, e);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">seckillVoucher</span><span class="params">(Long voucherId)</span> &#123;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">result</span> <span class="operator">=</span> stringRedisTemplate.execute(SECKILL_SCRIPT,</span><br><span class="line">                Collections.emptyList(), voucherId.toString(),</span><br><span class="line">                UserHolder.getUser().getId().toString());</span><br><span class="line">        <span class="keyword">if</span> (result.intValue() != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> Result.fail(result.intValue() == <span class="number">1</span> ? <span class="string">&quot;库存不足&quot;</span> : <span class="string">&quot;不能重复下单&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">long</span> <span class="variable">orderId</span> <span class="operator">=</span> redisIdWorker.nextId(<span class="string">&quot;order&quot;</span>);</span><br><span class="line">        <span class="comment">//封装到voucherOrder中</span></span><br><span class="line">        <span class="type">VoucherOrder</span> <span class="variable">voucherOrder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">VoucherOrder</span>();</span><br><span class="line">        voucherOrder.setVoucherId(voucherId);</span><br><span class="line">        voucherOrder.setUserId(UserHolder.getUser().getId());</span><br><span class="line">        voucherOrder.setId(orderId);</span><br><span class="line">        <span class="comment">//加入到阻塞队列</span></span><br><span class="line">        orderTasks.add(voucherOrder);</span><br><span class="line">        <span class="comment">//主线程获取代理对象</span></span><br><span class="line">        <span class="comment">// proxy = (IVoucherOrderService) AopContext.currentProxy();</span></span><br><span class="line">        <span class="keyword">return</span> Result.ok(orderId);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createVoucherOrder</span><span class="params">(VoucherOrder voucherOrder)</span> &#123;</span><br><span class="line">        <span class="comment">// 一人一单逻辑</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> voucherOrder.getUserId();</span><br><span class="line">        <span class="type">Long</span> <span class="variable">voucherId</span> <span class="operator">=</span> voucherOrder.getVoucherId();</span><br><span class="line">        <span class="keyword">synchronized</span> (userId.toString().intern()) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> query().eq(<span class="string">&quot;voucher_id&quot;</span>, voucherId).eq(<span class="string">&quot;user_id&quot;</span>, userId).count();</span><br><span class="line">            <span class="keyword">if</span> (count &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                log.error(<span class="string">&quot;你已经抢过优惠券了哦&quot;</span>);</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//5. 扣减库存</span></span><br><span class="line">            <span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> seckillVoucherService.update()</span><br><span class="line">                    .setSql(<span class="string">&quot;stock = stock - 1&quot;</span>)</span><br><span class="line">                    .eq(<span class="string">&quot;voucher_id&quot;</span>, voucherId)</span><br><span class="line">                    .gt(<span class="string">&quot;stock&quot;</span>, <span class="number">0</span>)</span><br><span class="line">                    .update();</span><br><span class="line">            <span class="keyword">if</span> (!success) &#123;</span><br><span class="line">                log.error(<span class="string">&quot;库存不足&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//7. 将订单数据保存到表中</span></span><br><span class="line">            save(voucherOrder);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="blocking-queue-4"><p><strong>阻塞队列方案优缺点</strong></p><div class="note warning flat"><p><strong>内存队列的局限性</strong></p></div><p><strong>优点</strong>：</p><ul><li>✅ <strong>实现简单</strong>：JDK原生支持，无需额外依赖</li><li>✅ <strong>性能优秀</strong>：内存操作，延迟极低</li><li>✅ <strong>削峰填谷</strong>：缓冲瞬时高并发请求</li></ul><p><strong>缺点</strong>：</p><ul><li>❌ <strong>内存限制</strong>：队列容量有限，数据可能丢失</li><li>❌ <strong>单点故障</strong>：JVM宕机导致队列数据丢失</li><li>❌ <strong>扩展性差</strong>：无法分布式部署，只能单机处理</li></ul><p><strong>适用场景</strong>：</p><ul><li>✅ <strong>单机部署</strong>：应用部署在单台服务器</li><li>✅ <strong>数据可接受丢失</strong>：秒杀活动数据允许少量丢失</li><li>❌ <strong>分布式部署</strong>：需要多机协同处理</li><li>❌ <strong>数据强一致性</strong>：订单数据不能丢失</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p><strong>小总结：</strong></p><p>秒杀业务的优化思路是什么？</p><ul><li>先利用Redis完成库存余量、一人一单判断，完成抢单业务</li><li>再将下单业务放入阻塞队列，利用独立线程异步下单</li><li>基于阻塞队列的异步秒杀存在哪些问题？<ul><li>内存限制问题</li><li>数据安全问题</li></ul></li></ul><h2 id="7-Redis消息队列">7. Redis消息队列</h2><h3 id="7-1-1-消息队列基础">7.1.1 消息队列基础</h3><div class="tabs" id="mq-basics"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#mq-basics-1">基本概念</button></li><li class="tab"><button type="button" data-href="#mq-basics-2">使用场景</button></li><li class="tab"><button type="button" data-href="#mq-basics-3">Redis实现方案</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="mq-basics-1"><p><strong>什么是消息队列</strong></p><div class="note info flat"><p><strong>消息队列的核心概念</strong></p></div><p><strong>定义</strong>：消息队列是一种异步通信机制，用于在不同组件或系统之间传递消息。</p><p><strong>三个核心角色</strong>：</p><ul><li><strong>消息队列</strong>：存储和管理消息的消息代理（Message Broker）</li><li><strong>生产者</strong>：发送消息到消息队列的应用或服务</li><li><strong>消费者</strong>：从消息队列获取消息并处理的应用或服务</li></ul><p><strong>通信模型</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">生产者 → 消息队列 → 消费者</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="mq-basics-2"><p><strong>消息队列的使用场景</strong></p><div class="note success flat"><p><strong>解耦和异步处理</strong></p></div><p><strong>生活例子</strong>：快递柜系统</p><ul><li><strong>快递员（生产者）</strong>：把快递放入快递柜</li><li><strong>快递柜（消息队列）</strong>：临时存储快递</li><li><strong>用户（消费者）</strong>：从快递柜取快递</li></ul><p><strong>技术优势</strong>：</p><ul><li><strong>解耦</strong>：生产者和消费者不需要直接通信</li><li><strong>异步</strong>：生产者不需要等待消费者处理完成</li><li><strong>削峰填谷</strong>：缓冲瞬时高并发请求</li><li><strong>可靠性</strong>：消息持久化，确保不丢失</li></ul><p><strong>秒杀场景应用</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">下单校验 → Redis队列 → 异步处理订单</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="mq-basics-3"><p><strong>Redis消息队列方案</strong></p><div class="note warning flat"><p><strong>Redis提供的三种消息队列实现</strong></p></div><table><thead><tr><th>实现方式</th><th>数据结构</th><th>特点</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>List队列</strong></td><td>List</td><td>简单可靠，支持阻塞</td><td>简单消息传递</td></tr><tr><td><strong>PubSub</strong></td><td>发布订阅</td><td>实时推送，不支持持久化</td><td>实时通知</td></tr><tr><td><strong>Stream</strong></td><td>Stream</td><td>功能完善，支持持久化</td><td>复杂消息系统</td></tr></tbody></table><p><strong>选择建议</strong>：</p><ul><li>✅ <strong>简单场景</strong>：使用List实现</li><li>✅ <strong>实时通知</strong>：使用PubSub</li><li>✅ <strong>复杂业务</strong>：使用Stream</li><li>❌ <strong>大数据量</strong>：建议使用专业MQ（Kafka、RabbitMQ）</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="7-1-2-基于List实现消息队列">7.1.2 基于List实现消息队列</h3><div class="tabs" id="list-mq"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#list-mq-1">实现原理</button></li><li class="tab"><button type="button" data-href="#list-mq-2">代码示例</button></li><li class="tab"><button type="button" data-href="#list-mq-3">优缺点分析</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="list-mq-1"><p><strong>List结构消息队列原理</strong></p><div class="note info flat"><p><strong>双向链表实现队列效果</strong></p></div><p><strong>基本原理</strong>：Redis的List是双向链表，天然支持队列操作</p><p><strong>操作命令</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># 生产者：从左侧入队</span><br><span class="line">LPUSH queue_name message</span><br><span class="line"></span><br><span class="line"># 消费者：从右侧出队  </span><br><span class="line">RPOP queue_name</span><br><span class="line"></span><br><span class="line"># 阻塞式消费（推荐）</span><br><span class="line">BRPOP queue_name timeout</span><br></pre></td></tr></table></figure><p><strong>队列模型</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">生产者 → LPUSH → [message3, message2, message1] → RPOP → 消费者</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="list-mq-2"><p><strong>List消息队列实现</strong></p><p><strong>生产者代码</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 发送消息到队列</span></span><br><span class="line">stringRedisTemplate.opsForList().leftPush(<span class="string">&quot;queue:order&quot;</span>, message);</span><br></pre></td></tr></table></figure><p><strong>消费者代码</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 阻塞式获取消息（超时5秒）</span></span><br><span class="line"><span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> stringRedisTemplate.opsForList()</span><br><span class="line">    .rightPop(<span class="string">&quot;queue:order&quot;</span>, <span class="number">5</span>, TimeUnit.SECONDS);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (message != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="comment">// 处理消息</span></span><br><span class="line">    processMessage(message);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>批量处理</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 一次获取多条消息</span></span><br><span class="line">List&lt;String&gt; messages = stringRedisTemplate.opsForList()</span><br><span class="line">    .range(<span class="string">&quot;queue:order&quot;</span>, <span class="number">0</span>, <span class="number">99</span>);</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="list-mq-3"><p><strong>List消息队列优缺点</strong></p><div class="note warning flat"><p><strong>简单但功能有限</strong></p></div><p><strong>优点</strong>：</p><ul><li>✅ <strong>实现简单</strong>：Redis原生List结构，无需额外配置</li><li>✅ <strong>持久化支持</strong>：基于Redis持久化，数据安全</li><li>✅ <strong>有序性</strong>：消息按入队顺序消费</li><li>✅ <strong>内存大</strong>：不受JVM内存限制</li></ul><p><strong>缺点</strong>：</p><ul><li>❌ <strong>消息丢失</strong>：消费者处理失败时消息已删除</li><li>❌ <strong>单消费者</strong>：一条消息只能被一个消费者处理</li><li>❌ <strong>无ACK机制</strong>：无法确认消息是否成功处理</li><li>❌ <strong>无重试机制</strong>：处理失败的消息无法重新消费</li></ul><p><strong>改进方案</strong>：</p><ul><li><strong>消息确认</strong>：消费后不立即删除，先放入&quot;处理中&quot;列表</li><li><strong>失败重试</strong>：处理失败的消息重新放回队列</li><li><strong>多消费者</strong>：使用多个队列实现消费者组</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="7-1-3-基于PubSub实现消息队列">7.1.3 基于PubSub实现消息队列</h3><div class="tabs" id="pubsub-mq"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#pubsub-mq-1">基本原理</button></li><li class="tab"><button type="button" data-href="#pubsub-mq-2">代码示例</button></li><li class="tab"><button type="button" data-href="#pubsub-mq-3">优缺点分析</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="pubsub-mq-1"><p><strong>PubSub发布订阅模型</strong></p><div class="note info flat"><p><strong>频道(Channel)广播机制</strong></p></div><p><strong>核心概念</strong>：</p><ul><li><strong>频道(Channel)</strong>：消息发布的通道</li><li><strong>订阅者(Subscriber)</strong>：订阅频道的客户端</li><li><strong>发布者(Publisher)</strong>：向频道发送消息的客户端</li></ul><p><strong>基本命令</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># 订阅频道</span><br><span class="line">SUBSCRIBE channel1 channel2</span><br><span class="line"></span><br><span class="line"># 发布消息</span><br><span class="line">PUBLISH channel1 &quot;hello world&quot;</span><br><span class="line"></span><br><span class="line"># 模式订阅（通配符）</span><br><span class="line">PSUBSCRIBE news.*</span><br></pre></td></tr></table></figure><p><strong>消息流</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">发布者 → PUBLISH → Channel → 广播 → 所有订阅者</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="pubsub-mq-2"><p><strong>PubSub消息队列实现</strong></p><p><strong>消息发布者</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 发布消息</span></span><br><span class="line">stringRedisTemplate.convertAndSend(<span class="string">&quot;order.channel&quot;</span>, orderMessage);</span><br></pre></td></tr></table></figure><p><strong>消息订阅者</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderMessageSubscriber</span> <span class="keyword">extends</span> <span class="title class_">KeyspaceEventMessageListener</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">OrderMessageSubscriber</span><span class="params">(RedisMessageListenerContainer listenerContainer)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(listenerContainer);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doHandleMessage</span><span class="params">(Message message)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">channel</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(message.getChannel());</span><br><span class="line">        <span class="type">String</span> <span class="variable">body</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(message.getBody());</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (<span class="string">&quot;order.channel&quot;</span>.equals(channel)) &#123;</span><br><span class="line">            <span class="comment">// 处理订单消息</span></span><br><span class="line">            processOrderMessage(body);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>配置监听器</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> RedisMessageListenerContainer <span class="title function_">container</span><span class="params">(</span></span><br><span class="line"><span class="params">        RedisConnectionFactory connectionFactory,</span></span><br><span class="line"><span class="params">        OrderMessageSubscriber subscriber)</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="type">RedisMessageListenerContainer</span> <span class="variable">container</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RedisMessageListenerContainer</span>();</span><br><span class="line">    container.setConnectionFactory(connectionFactory);</span><br><span class="line">    container.addMessageListener(subscriber, <span class="keyword">new</span> <span class="title class_">ChannelTopic</span>(<span class="string">&quot;order.channel&quot;</span>));</span><br><span class="line">    <span class="keyword">return</span> container;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="pubsub-mq-3"><p><strong>PubSub消息队列优缺点</strong></p><div class="note danger flat"><p><strong>实时但不可靠</strong></p></div><p><strong>优点</strong>：</p><ul><li>✅ <strong>实时性</strong>：消息立即推送给所有订阅者</li><li>✅ <strong>多订阅者</strong>：一个消息可被多个消费者同时接收</li><li>✅ <strong>简单易用</strong>：Redis原生支持，配置简单</li><li>✅ <strong>模式匹配</strong>：支持通配符订阅多个频道</li></ul><p><strong>缺点</strong>：</p><ul><li>❌ <strong>无持久化</strong>：消息不存储，订阅者离线时消息丢失</li><li>❌ <strong>无确认机制</strong>：无法保证消息被成功处理</li><li>❌ <strong>无重试机制</strong>：处理失败的消息无法重新投递</li><li>❌ <strong>内存压力</strong>：大量订阅者可能导致内存问题</li></ul><p><strong>适用场景</strong>：</p><ul><li>✅ <strong>实时通知</strong>：系统状态变更、配置更新等</li><li>✅ <strong>广播消息</strong>：需要多个消费者同时接收的场景</li><li>❌ <strong>重要业务</strong>：订单处理、支付等关键业务</li><li>❌ <strong>离线处理</strong>：消费者可能离线的场景</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p>基于PubSub的消息队列有哪些优缺点？<br>优点：</p><ul><li>采用发布订阅模型，支持多生产、多消费</li></ul><p>缺点：</p><ul><li>不支持数据持久化</li><li>无法避免消息丢失</li><li>消息堆积有上限，超出时数据丢失</li></ul><h3 id="7-1-4-基于Stream实现消息队列">7.1.4 基于Stream实现消息队列</h3><div class="tabs" id="stream-mq"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#stream-mq-1">基本原理</button></li><li class="tab"><button type="button" data-href="#stream-mq-2">代码示例</button></li><li class="tab"><button type="button" data-href="#stream-mq-3">优缺点分析</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="stream-mq-1"><p><strong>实现原理</strong></p><p>Stream 是 Redis 5.0 引入的新数据类型，专门用于实现消息队列功能：</p><ul><li><strong>消息存储</strong>：每个消息都有唯一ID，格式为<code>时间戳-序列号</code></li><li><strong>消费者组</strong>：支持多消费者组同时消费，互不干扰</li><li><strong>ACK机制</strong>：消费者处理完消息后需要确认，保证消息至少消费一次</li></ul><p><strong>核心命令</strong>：</p><ul><li><code>XADD</code>：发送消息</li><li><code>XREAD</code>：读取消息</li><li><code>XREADGROUP</code>：消费者组读取</li><li><code>XACK</code>：消息确认</li></ul><div class="note flat"><p>Stream相比List和PubSub，提供了更完善的消息队列功能，支持消息持久化、消费者组和ACK机制。</p></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="stream-mq-2"><p><strong>代码示例</strong></p><p><strong>生产者发送消息</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 发送消息到Stream</span></span><br><span class="line">Map&lt;String, Object&gt; message = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">message.put(<span class="string">&quot;voucherId&quot;</span>, voucherId);</span><br><span class="line">message.put(<span class="string">&quot;userId&quot;</span>, userId);</span><br><span class="line">message.put(<span class="string">&quot;orderId&quot;</span>, orderId);</span><br><span class="line"></span><br><span class="line"><span class="type">String</span> <span class="variable">recordId</span> <span class="operator">=</span> stringRedisTemplate.opsForStream()</span><br><span class="line">    .add(<span class="string">&quot;stream.orders&quot;</span>, message);</span><br></pre></td></tr></table></figure><p><strong>消费者组消费</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">class</span> <span class="title class_">VoucherOrderHandler</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 从消费者组读取消息</span></span><br><span class="line">                List&lt;MapRecord&lt;String, Object, Object&gt;&gt; list = stringRedisTemplate.opsForStream()</span><br><span class="line">                    .read(Consumer.from(<span class="string">&quot;g1&quot;</span>, <span class="string">&quot;c1&quot;</span>),</span><br><span class="line">                          StreamReadOptions.empty().count(<span class="number">1</span>).block(Duration.ofSeconds(<span class="number">2</span>)),</span><br><span class="line">                          StreamOffset.create(<span class="string">&quot;stream.orders&quot;</span>, ReadOffset.lastConsumed()));</span><br><span class="line">                </span><br><span class="line">                <span class="keyword">if</span> (list == <span class="literal">null</span> || list.isEmpty()) &#123;</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 处理消息</span></span><br><span class="line">                MapRecord&lt;String, Object, Object&gt; record = list.get(<span class="number">0</span>);</span><br><span class="line">                Map&lt;Object, Object&gt; value = record.getValue();</span><br><span class="line">                <span class="type">VoucherOrder</span> <span class="variable">voucherOrder</span> <span class="operator">=</span> BeanUtil.fillBeanWithMap(value, <span class="keyword">new</span> <span class="title class_">VoucherOrder</span>(), <span class="literal">true</span>);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 创建订单</span></span><br><span class="line">                createVoucherOrder(voucherOrder);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 确认消息</span></span><br><span class="line">                stringRedisTemplate.opsForStream().acknowledge(<span class="string">&quot;stream.orders&quot;</span>, <span class="string">&quot;g1&quot;</span>, record.getId());</span><br><span class="line">                </span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                log.error(<span class="string">&quot;处理订单异常&quot;</span>, e);</span><br><span class="line">                handlePendingList(); <span class="comment">// 处理异常消息</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 处理pending-list中的异常消息</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">handlePendingList</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                List&lt;MapRecord&lt;String, Object, Object&gt;&gt; list = stringRedisTemplate.opsForStream()</span><br><span class="line">                    .read(Consumer.from(<span class="string">&quot;g1&quot;</span>, <span class="string">&quot;c1&quot;</span>),</span><br><span class="line">                          StreamReadOptions.empty().count(<span class="number">1</span>),</span><br><span class="line">                          StreamOffset.create(<span class="string">&quot;stream.orders&quot;</span>, ReadOffset.from(<span class="string">&quot;0&quot;</span>)));</span><br><span class="line">                </span><br><span class="line">                <span class="keyword">if</span> (list == <span class="literal">null</span> || list.isEmpty()) &#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                </span><br><span class="line">                MapRecord&lt;String, Object, Object&gt; record = list.get(<span class="number">0</span>);</span><br><span class="line">                Map&lt;Object, Object&gt; value = record.getValue();</span><br><span class="line">                <span class="type">VoucherOrder</span> <span class="variable">voucherOrder</span> <span class="operator">=</span> BeanUtil.fillBeanWithMap(value, <span class="keyword">new</span> <span class="title class_">VoucherOrder</span>(), <span class="literal">true</span>);</span><br><span class="line">                </span><br><span class="line">                createVoucherOrder(voucherOrder);</span><br><span class="line">                stringRedisTemplate.opsForStream().acknowledge(<span class="string">&quot;stream.orders&quot;</span>, <span class="string">&quot;g1&quot;</span>, record.getId());</span><br><span class="line">                </span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                log.error(<span class="string">&quot;处理pending订单异常&quot;</span>, e);</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    Thread.sleep(<span class="number">20</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">                    ex.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="stream-mq-3"><p><strong>优缺点分析</strong></p><table><thead><tr><th>特性</th><th>Stream</th><th>说明</th></tr></thead><tbody><tr><td>消息持久化</td><td>✅ 支持</td><td>消息保存在内存和磁盘中</td></tr><tr><td>消息回溯</td><td>✅ 支持</td><td>可以读取历史消息</td></tr><tr><td>消费者组</td><td>✅ 支持</td><td>多组消费者独立消费</td></tr><tr><td>ACK机制</td><td>✅ 支持</td><td>保证消息至少消费一次</td></tr><tr><td>消息堆积</td><td>✅ 支持</td><td>内存足够时可堆积大量消息</td></tr><tr><td>实现复杂度</td><td>⭐⭐⭐ 中等</td><td>需要理解消费者组概念</td></tr></tbody></table><p><strong>适用场景</strong>：</p><ul><li>需要消息持久化的业务场景</li><li>多消费者组独立消费</li><li>消息处理可靠性要求高的场景</li><li>复杂的秒杀订单处理</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="7-1-5-消息队列对比总结">7.1.5 消息队列对比总结</h3><div class="tabs" id="mq-compare"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#mq-compare-1">功能特性对比</button></li><li class="tab"><button type="button" data-href="#mq-compare-2">性能对比</button></li><li class="tab"><button type="button" data-href="#mq-compare-3">选择建议</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="mq-compare-1"><p><strong>功能特性对比</strong></p><table><thead><tr><th>功能特性</th><th>List结构</th><th>PubSub</th><th>Stream</th></tr></thead><tbody><tr><td>消息持久化</td><td>✅ 支持</td><td>❌ 不支持</td><td>✅ 支持</td></tr><tr><td>消息回溯</td><td>❌ 不支持</td><td>❌ 不支持</td><td>✅ 支持</td></tr><tr><td>消费者组</td><td>❌ 不支持</td><td>❌ 不支持</td><td>✅ 支持</td></tr><tr><td>消息确认</td><td>❌ 不支持</td><td>❌ 不支持</td><td>✅ 支持</td></tr><tr><td>阻塞读取</td><td>✅ 支持</td><td>✅ 支持</td><td>✅ 支持</td></tr><tr><td>消息堆积</td><td>受内存限制</td><td>受内存限制</td><td>受内存限制</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="mq-compare-2"><p><strong>性能对比</strong></p><table><thead><tr><th>性能指标</th><th>List</th><th>PubSub</th><th>Stream</th></tr></thead><tbody><tr><td>吞吐量</td><td>高</td><td>最高</td><td>高</td></tr><tr><td>延迟</td><td>低</td><td>最低</td><td>低</td></tr><tr><td>CPU消耗</td><td>低</td><td>最低</td><td>中等</td></tr><tr><td>内存使用</td><td>低</td><td>低</td><td>中等</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="mq-compare-3"><p><strong>选择建议</strong></p><ul><li><strong>List队列</strong>：简单的任务队列，对可靠性要求不高</li><li><strong>PubSub</strong>：实时消息推送，允许消息丢失的场景</li><li><strong>Stream</strong>：业务消息队列，需要高可靠性和完整功能</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="8-达人探店">8. 达人探店</h2><div class="note flat"><p>发布探店笔记</p><p>探店笔记类似点评网站的评价，往往是图文结合。对应的表有两个：<br>tb_blog：探店笔记表，包含笔记中的标题、文字、图片等</p><p><strong>tb_blog表结构：</strong></p><table><thead><tr><th>Field</th><th>Type</th><th>Collation</th><th>Null</th><th>Key</th><th>Default</th><th>Extra</th><th>Comment</th></tr></thead><tbody><tr><td>id</td><td>bigint unsigned</td><td>(NULL)</td><td>NO</td><td>PRI</td><td>(NULL)</td><td>auto_increment</td><td>主键</td></tr><tr><td>shop_id</td><td>bigint</td><td>(NULL)</td><td>NO</td><td></td><td>(NULL)</td><td></td><td>商户id</td></tr><tr><td>user_id</td><td>bigint unsigned</td><td>(NULL)</td><td>NO</td><td></td><td>(NULL)</td><td></td><td>用户id</td></tr><tr><td>title</td><td>varchar(255)</td><td>utf8mb4_unicode_ci</td><td>NO</td><td></td><td>(NULL)</td><td></td><td>标题</td></tr><tr><td>images</td><td>varchar(2048)</td><td>utf8mb4_general_ci</td><td>NO</td><td></td><td>(NULL)</td><td></td><td>探店的照片，最多9张，多张以&quot;,&quot;隔开</td></tr><tr><td>content</td><td>varchar(2048)</td><td>utf8mb4_unicode_ci</td><td>NO</td><td></td><td>(NULL)</td><td></td><td>探店的文字描述</td></tr><tr><td>liked</td><td>int unsigned</td><td>(NULL)</td><td>YES</td><td></td><td>0</td><td></td><td>点赞数量</td></tr><tr><td>comments</td><td>int unsigned</td><td>(NULL)</td><td>YES</td><td></td><td>(NULL)</td><td></td><td>评论数量</td></tr><tr><td>create_time</td><td>timestamp</td><td>(NULL)</td><td>NO</td><td></td><td>CURRENT_TIMESTAMP</td><td>DEFAULT_GENERATED</td><td>创建时间</td></tr><tr><td>update_time</td><td>timestamp</td><td>(NULL)</td><td>NO</td><td></td><td>CURRENT_TIMESTAMP</td><td>DEFAULT_GENERATED on update CURRENT_TIMESTAMP</td><td>更新时间</td></tr></tbody></table><p>tb_blog_comments：其他用户对探店笔记的评价</p><p><strong>tb_blog_comments表结构：</strong></p><table><thead><tr><th>Field</th><th>Type</th><th>Collation</th><th>Null</th><th>Key</th><th>Default</th><th>Extra</th><th>Comment</th></tr></thead><tbody><tr><td>id</td><td>bigint unsigned</td><td>(NULL)</td><td>NO</td><td>PRI</td><td>(NULL)</td><td>auto_increment</td><td>主键</td></tr><tr><td>user_id</td><td>bigint unsigned</td><td>(NULL)</td><td>NO</td><td></td><td>(NULL)</td><td></td><td>用户id</td></tr><tr><td>blog_id</td><td>bigint unsigned</td><td>(NULL)</td><td>NO</td><td></td><td>(NULL)</td><td></td><td>探店id</td></tr><tr><td>parent_id</td><td>bigint unsigned</td><td>(NULL)</td><td>NO</td><td></td><td>(NULL)</td><td></td><td>关联的1级评论id，如果是一级评论，则值为0</td></tr><tr><td>answer_id</td><td>bigint unsigned</td><td>(NULL)</td><td>NO</td><td></td><td>(NULL)</td><td></td><td>回复的评论id</td></tr><tr><td>content</td><td>varchar(255)</td><td>utf8mb4_general_ci</td><td>NO</td><td></td><td>(NULL)</td><td></td><td>回复的内容</td></tr><tr><td>liked</td><td>int unsigned</td><td>(NULL)</td><td>YES</td><td></td><td>(NULL)</td><td></td><td>点赞数</td></tr><tr><td>status</td><td>tinyint unsigned</td><td>(NULL)</td><td>YES</td><td></td><td>(NULL)</td><td></td><td>状态，0：正常，1：被举报，2：禁止查看</td></tr><tr><td>create_time</td><td>timestamp</td><td>(NULL)</td><td>NO</td><td></td><td>CURRENT_TIMESTAMP</td><td>DEFAULT_GENERATED</td><td>创建时间</td></tr><tr><td>update_time</td><td>timestamp</td><td>(NULL)</td><td>NO</td><td></td><td>CURRENT_TIMESTAMP</td><td>DEFAULT_GENERATED on update CURRENT_TIMESTAMP</td><td>更新时间</td></tr></tbody></table><p>达人探店功能允许用户发布探店笔记，包括图片上传、笔记发布、点赞互动等功能。本章节将介绍如何使用Redis优化这些功能的实现。</p></div><p><strong>对应的实体类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@EqualsAndHashCode(callSuper = false)</span></span><br><span class="line"><span class="meta">@Accessors(chain = true)</span></span><br><span class="line"><span class="meta">@TableName(&quot;tb_blog&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Blog</span> <span class="keyword">implements</span> <span class="title class_">Serializable</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">serialVersionUID</span> <span class="operator">=</span> <span class="number">1L</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 主键</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@TableId(value = &quot;id&quot;, type = IdType.AUTO)</span></span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 商户id</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> Long shopId;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 用户id</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> Long userId;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 用户图标</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@TableField(exist = false)</span></span><br><span class="line">    <span class="keyword">private</span> String icon;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 用户姓名</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@TableField(exist = false)</span></span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 是否点赞过了</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@TableField(exist = false)</span></span><br><span class="line">    <span class="keyword">private</span> Boolean isLike;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 标题</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> String title;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 探店的照片，最多9张，多张以&quot;,&quot;隔开</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> String images;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 探店的文字描述</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> String content;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 点赞数量</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> Integer liked;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 评论数量</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> Integer comments;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 创建时间</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> LocalDateTime createTime;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 更新时间</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> LocalDateTime updateTime;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="8-1-图片上传与笔记发布">8.1 图片上传与笔记发布</h3><div class="tabs" id="blog-upload"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#blog-upload-1">功能概述</button></li><li class="tab"><button type="button" data-href="#blog-upload-2">代码实现</button></li><li class="tab"><button type="button" data-href="#blog-upload-3">关键点分析</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="blog-upload-1"><p>探店笔记发布包含两个核心功能：</p><ul><li><strong>图片上传</strong>：支持单张图片上传，生成唯一文件名</li><li><strong>笔记发布</strong>：保存探店博文，关联上传的图片</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="blog-upload-2"><p><strong>图片上传控制器</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;upload&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UploadController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostMapping(&quot;blog&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">uploadImage</span><span class="params">(<span class="meta">@RequestParam(&quot;file&quot;)</span> MultipartFile image)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 获取原始文件名称</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">originalFilename</span> <span class="operator">=</span> image.getOriginalFilename();</span><br><span class="line">            <span class="comment">// 生成新文件名</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">fileName</span> <span class="operator">=</span> createNewFileName(originalFilename);</span><br><span class="line">            <span class="comment">// 保存文件</span></span><br><span class="line">            image.transferTo(<span class="keyword">new</span> <span class="title class_">File</span>(SystemConstants.IMAGE_UPLOAD_DIR, fileName));</span><br><span class="line">            <span class="comment">// 返回结果</span></span><br><span class="line">            log.debug(<span class="string">&quot;文件上传成功，&#123;&#125;&quot;</span>, fileName);</span><br><span class="line">            <span class="keyword">return</span> Result.ok(fileName);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;文件上传失败&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>笔记发布控制器</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/blog&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BlogController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> IBlogService blogService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostMapping</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">saveBlog</span><span class="params">(<span class="meta">@RequestBody</span> Blog blog)</span> &#123;</span><br><span class="line">        <span class="comment">//获取登录用户</span></span><br><span class="line">        <span class="type">UserDTO</span> <span class="variable">user</span> <span class="operator">=</span> UserHolder.getUser();</span><br><span class="line">        blog.setUpdateTime(user.getId());</span><br><span class="line">        <span class="comment">//保存探店博文</span></span><br><span class="line">        blogService.saveBlog(blog);</span><br><span class="line">        <span class="comment">//返回id</span></span><br><span class="line">        <span class="keyword">return</span> Result.ok(blog.getId());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="note flat"><p>注意：需要修改<code>SystemConstants.IMAGE_UPLOAD_DIR</code>为实际的图片存储路径，生产环境中建议使用云存储服务。</p></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="blog-upload-3"><ol><li><strong>文件命名策略</strong>：使用UUID确保文件名唯一性，避免冲突</li><li><strong>异常处理</strong>：捕获IO异常并转换为业务异常</li><li><strong>用户认证</strong>：通过<code>UserHolder</code>获取当前登录用户信息</li><li><strong>返回值设计</strong>：返回生成的文件名，供前端展示和后续业务使用</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="8-2-查看探店笔记">8.2 查看探店笔记</h3><div class="tabs" id="blog-query"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#blog-query-1">功能概述</button></li><li class="tab"><button type="button" data-href="#blog-query-2">代码实现</button></li><li class="tab"><button type="button" data-href="#blog-query-3">关键点分析</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="blog-query-1"><p>查看探店笔记功能需要：</p><ul><li><strong>笔记查询</strong>：根据ID查询笔记详情</li><li><strong>用户信息</strong>：查询笔记作者信息</li><li><strong>点赞状态</strong>：判断当前用户是否点赞</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="blog-query-2"><p><strong>controller</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;/hot&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">queryHotBlog</span><span class="params">(<span class="meta">@RequestParam(value = &quot;current&quot;, defaultValue = &quot;1&quot;)</span> Integer current)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> blogService.queryHotBlog(current);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryById</span><span class="params">(<span class="meta">@PathVariable</span> Integer id)</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> blogService.queryById(id);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>service</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryHotBlog</span><span class="params">(Integer current)</span> &#123;</span><br><span class="line">    <span class="comment">// 根据用户查询</span></span><br><span class="line">    Page&lt;Blog&gt; page = query()</span><br><span class="line">            .orderByDesc(<span class="string">&quot;liked&quot;</span>)</span><br><span class="line">            .page(<span class="keyword">new</span> <span class="title class_">Page</span>&lt;&gt;(current, SystemConstants.MAX_PAGE_SIZE));</span><br><span class="line">    <span class="comment">// 获取当前页数据</span></span><br><span class="line">    List&lt;Blog&gt; records = page.getRecords();</span><br><span class="line">    <span class="comment">// 查询用户</span></span><br><span class="line">    records.forEach(<span class="built_in">this</span>::queryBlogUser);</span><br><span class="line">    <span class="keyword">return</span> Result.ok(records);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryById</span><span class="params">(Integer id)</span> &#123;</span><br><span class="line">    <span class="type">Blog</span> <span class="variable">blog</span> <span class="operator">=</span> getById(id);</span><br><span class="line">    <span class="keyword">if</span> (blog == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;评价不存在或已被删除&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    queryBlogUser(blog);</span><br><span class="line">    <span class="keyword">return</span> Result.ok(blog);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">queryBlogUser</span><span class="params">(Blog blog)</span> &#123;</span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> blog.getUserId();</span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userService.getById(userId);</span><br><span class="line">    blog.setName(user.getNickName());</span><br><span class="line">    blog.setIcon(user.getIcon());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="blog-query-3"><ol><li><strong>数据完整性</strong>：先查询笔记，再查询关联的用户信息</li><li><strong>异常处理</strong>：笔记不存在时给出友好提示</li><li><strong>数据组装</strong>：<code>queryBlogUser</code>方法负责填充用户相关信息</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="8-3-点赞功能优化">8.3 点赞功能优化</h3><div class="tabs" id="blog-like"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#blog-like-1">功能概述</button></li><li class="tab"><button type="button" data-href="#blog-like-2">需求优化</button></li><li class="tab"><button type="button" data-href="#blog-like-3">代码实现</button></li><li class="tab"><button type="button" data-href="#blog-like-4">发送请求</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="blog-like-1"><p>初始点赞实现存在严重问题：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;/likes/&#123;id&#125;&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryBlogLikes</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long id)</span> &#123;</span><br><span class="line">    <span class="comment">//修改点赞数量</span></span><br><span class="line">    blogService.update().setSql(<span class="string">&quot;liked = liked +1 &quot;</span>).eq(<span class="string">&quot;id&quot;</span>,id).update();</span><br><span class="line">    <span class="keyword">return</span> Result.ok();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>问题</strong>：用户可以无限点赞，没有任何限制机制</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="blog-like-2"><ul><li>同一个用户只能点赞一次，再次点击则取消点赞</li><li>如果当前用户已经点赞，则点赞按钮高亮显示</li><li>使用Redis的Set集合记录点赞用户，保证唯一性</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="blog-like-3"><p><strong>1. 修改Blog实体类</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@TableField(exist = false)</span></span><br><span class="line"><span class="keyword">private</span> Boolean isLike; <span class="comment">// 是否被当前用户点赞</span></span><br></pre></td></tr></table></figure><p><strong>2. 点赞业务逻辑</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@PutMapping(&quot;/like/&#123;id&#125;&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">likeBlog</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long id)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> blogService.likeBlog(id);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">likeBlog</span><span class="params">(Long id)</span>&#123;</span><br><span class="line">    <span class="comment">// 1.获取登录用户</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="comment">// 2.判断当前登录用户是否已经点赞</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> BLOG_LIKED_KEY + id;</span><br><span class="line">    <span class="type">Boolean</span> <span class="variable">isMember</span> <span class="operator">=</span> stringRedisTemplate.opsForSet().isMember(key, userId.toString());</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span>(BooleanUtil.isFalse(isMember))&#123;</span><br><span class="line">        <span class="comment">//3.如果未点赞，可以点赞</span></span><br><span class="line">        <span class="comment">//3.1 数据库点赞数+1</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">isSuccess</span> <span class="operator">=</span> update().setSql(<span class="string">&quot;liked = liked + 1&quot;</span>).eq(<span class="string">&quot;id&quot;</span>, id).update();</span><br><span class="line">        <span class="comment">//3.2 保存用户到Redis的set集合</span></span><br><span class="line">        <span class="keyword">if</span>(isSuccess)&#123;</span><br><span class="line">            stringRedisTemplate.opsForSet().add(key,userId.toString());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">        <span class="comment">//4.如果已点赞，取消点赞</span></span><br><span class="line">        <span class="comment">//4.1 数据库点赞数-1</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">isSuccess</span> <span class="operator">=</span> update().setSql(<span class="string">&quot;liked = liked - 1&quot;</span>).eq(<span class="string">&quot;id&quot;</span>, id).update();</span><br><span class="line">        <span class="comment">//4.2 把用户从Redis的set集合移除</span></span><br><span class="line">        <span class="keyword">if</span>(isSuccess)&#123;</span><br><span class="line">            stringRedisTemplate.opsForSet().remove(key,userId.toString());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> Result.ok();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键点分析</strong></p><ol><li><strong>Redis Key设计</strong>：<code>blog:liked:{blogId}</code>，每个笔记一个独立的Set</li><li><strong>原子性保证</strong>：先判断再操作，通过Redis Set保证用户唯一性</li><li><strong>数据一致性</strong>：数据库和Redis同步更新，确保点赞数准确</li><li><strong>性能优化</strong>：Redis Set操作O(1)时间复杂度，性能优异</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="blog-like-4"><p><strong>点击点赞按钮，查看发送的请求</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">请求网址: http://localhost:8080/api/blog/like/4</span><br><span class="line">请求方法: PUT</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="8-4-点赞排行榜">8.4 点赞排行榜</h3><div class="tabs" id="blog-ranking"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#blog-ranking-1">功能概述</button></li><li class="tab"><button type="button" data-href="#blog-ranking-2">需求分析</button></li><li class="tab"><button type="button" data-href="#blog-ranking-3">代码实现</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="blog-ranking-1"><p>在探店笔记详情页面需要展示点赞排行榜，显示最早点赞的TOP5用户：</p><ul><li><strong>排序需求</strong>：按点赞时间排序，最早点赞的排在前面</li><li><strong>唯一性</strong>：每个用户只能出现一次</li><li><strong>性能要求</strong>：快速查询TOP5用户</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="blog-ranking-2"><p>使用Redis的SortedSet（ZSet）数据结构：</p><ul><li><strong>唯一性</strong>：ZSet保证成员唯一</li><li><strong>排序能力</strong>：根据score值排序，score使用时间戳</li><li><strong>范围查询</strong>：支持按排名范围查询</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="blog-ranking-3"><p><strong>1. 修改点赞逻辑（使用ZSet）</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">likeBlog</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.获取登录用户</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="comment">// 2.判断当前登录用户是否已经点赞</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> BLOG_LIKED_KEY + id;</span><br><span class="line">    <span class="type">Double</span> <span class="variable">score</span> <span class="operator">=</span> stringRedisTemplate.opsForZSet().score(key, userId.toString());</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (score == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 3.如果未点赞，可以点赞</span></span><br><span class="line">        <span class="comment">// 3.1.数据库点赞数 + 1</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">isSuccess</span> <span class="operator">=</span> update().setSql(<span class="string">&quot;liked = liked + 1&quot;</span>).eq(<span class="string">&quot;id&quot;</span>, id).update();</span><br><span class="line">        <span class="comment">// 3.2.保存用户到Redis的zset集合，score为当前时间戳</span></span><br><span class="line">        <span class="keyword">if</span> (isSuccess) &#123;</span><br><span class="line">            stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 4.如果已点赞，取消点赞</span></span><br><span class="line">        <span class="comment">// 4.1.数据库点赞数 -1</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">isSuccess</span> <span class="operator">=</span> update().setSql(<span class="string">&quot;liked = liked - 1&quot;</span>).eq(<span class="string">&quot;id&quot;</span>, id).update();</span><br><span class="line">        <span class="comment">// 4.2.把用户从Redis的zset集合移除</span></span><br><span class="line">        <span class="keyword">if</span> (isSuccess) &#123;</span><br><span class="line">            stringRedisTemplate.opsForZSet().remove(key, userId.toString());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> Result.ok();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>2. 查询点赞排行榜</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryBlogLikes</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> BLOG_LIKED_KEY + id;</span><br><span class="line">    <span class="comment">// 1.查询top5的点赞用户 zrange key 0 4</span></span><br><span class="line">    Set&lt;String&gt; top5 = stringRedisTemplate.opsForZSet().range(key, <span class="number">0</span>, <span class="number">4</span>);</span><br><span class="line">    <span class="keyword">if</span> (top5 == <span class="literal">null</span> || top5.isEmpty()) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.ok(Collections.emptyList());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 2.解析出其中的用户id</span></span><br><span class="line">    List&lt;Long&gt; ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());</span><br><span class="line">    <span class="type">String</span> <span class="variable">idStr</span> <span class="operator">=</span> StrUtil.join(<span class="string">&quot;,&quot;</span>, ids);</span><br><span class="line">    <span class="comment">// 3.根据用户id查询用户 WHERE id IN ( 5 , 1 ) ORDER BY FIELD(id, 5, 1)</span></span><br><span class="line">    List&lt;UserDTO&gt; userDTOS = userService.query()</span><br><span class="line">            .in(<span class="string">&quot;id&quot;</span>, ids).last(<span class="string">&quot;ORDER BY FIELD(id,&quot;</span> + idStr + <span class="string">&quot;)&quot;</span>).list()</span><br><span class="line">            .stream()</span><br><span class="line">            .map(user -&gt; BeanUtil.copyProperties(user, UserDTO.class))</span><br><span class="line">            .collect(Collectors.toList());</span><br><span class="line">    <span class="comment">// 4.返回</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok(userDTOS);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键点分析</strong></p><ol><li><strong>Score设计</strong>：使用时间戳作为score，自然按时间排序</li><li><strong>范围查询</strong>：<code>zrange key 0 4</code>获取前5个用户，时间复杂度O(log(n)+m)</li><li><strong>数据一致性</strong>：点赞和取消点赞时同步更新数据库和Redis</li><li><strong>内存优化</strong>：只存储用户ID，不存储完整用户信息</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="9-好友关注">9. 好友关注</h2><h3 id="9-1-关注和取消关注">9.1 关注和取消关注</h3><p>针对用户的操作：可以对用户进行关注和取消关注功能。</p><div class="tabs" id="follow-basic"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#follow-basic-1">功能概述</button></li><li class="tab"><button type="button" data-href="#follow-basic-2">代码实现</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="follow-basic-1"><p>好友关注功能实现用户之间的关注关系管理：</p><ul><li><strong>关注用户</strong>：用户A关注用户B</li><li><strong>取消关注</strong>：用户A取消对用户B的关注</li><li><strong>关注状态查询</strong>：查询用户A是否关注了用户B</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="follow-basic-2"><p><strong>1. 数据库表结构</strong>：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> `tb_follow`  (</span><br><span class="line">  `id` <span class="type">bigint</span>(<span class="number">0</span>) <span class="keyword">NOT NULL</span> AUTO_INCREMENT,</span><br><span class="line">  `user_id` <span class="type">bigint</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;用户id&#x27;</span>,</span><br><span class="line">  `follow_user_id` <span class="type">bigint</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;关联的用户id&#x27;</span>,</span><br><span class="line">  `create_time` <span class="type">timestamp</span> <span class="keyword">NOT NULL</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;创建时间&#x27;</span>,</span><br><span class="line">  <span class="keyword">PRIMARY KEY</span> (`id`),</span><br><span class="line">  <span class="keyword">UNIQUE</span> INDEX `idx_user_id_follow_user_id`(`user_id`, `follow_user_id`) <span class="keyword">USING</span> BTREE</span><br><span class="line">) ENGINE <span class="operator">=</span> InnoDB <span class="keyword">CHARACTER SET</span> <span class="operator">=</span> utf8mb4 <span class="keyword">COLLATE</span> <span class="operator">=</span> utf8mb4_general_ci COMMENT <span class="operator">=</span> <span class="string">&#x27;用户关注表&#x27;</span>;</span><br></pre></td></tr></table></figure><p><strong>2. 控制器层</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/follow&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FollowController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> IFollowService followService;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 关注和取消关注</span></span><br><span class="line">    <span class="meta">@PutMapping(&quot;/&#123;id&#125;/&#123;isFollow&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">follow</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long followUserId, </span></span><br><span class="line"><span class="params">                        <span class="meta">@PathVariable(&quot;isFollow&quot;)</span> Boolean isFollow)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> followService.follow(followUserId, isFollow);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 判断是否关注</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/or/not/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result <span class="title function_">isFollow</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long followUserId)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> followService.isFollow(followUserId);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>3. 业务逻辑层</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">isFollow</span><span class="params">(Long followUserId)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.获取登录用户</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="comment">// 2.查询是否关注 select count(*) from tb_follow where user_id = ? and follow_user_id = ?</span></span><br><span class="line">    <span class="type">Integer</span> <span class="variable">count</span> <span class="operator">=</span> query().eq(<span class="string">&quot;user_id&quot;</span>, userId).eq(<span class="string">&quot;follow_user_id&quot;</span>, followUserId).count();</span><br><span class="line">    <span class="comment">// 3.判断</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok(count &gt; <span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">follow</span><span class="params">(Long followUserId, Boolean isFollow)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.获取登录用户</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (isFollow) &#123;</span><br><span class="line">        <span class="comment">// 2.关注，新增数据</span></span><br><span class="line">        <span class="type">Follow</span> <span class="variable">follow</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Follow</span>();</span><br><span class="line">        follow.setUserId(userId);</span><br><span class="line">        follow.setFollowUserId(followUserId);</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">isSuccess</span> <span class="operator">=</span> save(follow);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 3.取关，删除数据</span></span><br><span class="line">        remove(<span class="keyword">new</span> <span class="title class_">QueryWrapper</span>&lt;Follow&gt;()</span><br><span class="line">                .eq(<span class="string">&quot;user_id&quot;</span>, userId).eq(<span class="string">&quot;follow_user_id&quot;</span>, followUserId));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> Result.ok();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键点分析</strong></p><ol><li><strong>唯一索引</strong>：<code>idx_user_id_follow_user_id</code>防止重复关注</li><li><strong>事务性</strong>：关注操作同时操作数据库，保证数据一致性</li><li><strong>幂等性</strong>：重复关注或取消关注不会产生副作用</li><li><strong>性能优化</strong>：关注状态查询使用count，比查询全表更高效</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="9-2-共同关注">9.2 共同关注</h3><div class="tabs" id="follow-common"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#follow-common-1">需求分析</button></li><li class="tab"><button type="button" data-href="#follow-common-2">技术方案</button></li><li class="tab"><button type="button" data-href="#follow-common-3">代码实现</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="follow-common-1"><p>在博主个人页面展示当前用户与博主的共同关注：</p><ul><li><strong>场景</strong>：用户A访问用户B的个人主页</li><li><strong>需求</strong>：显示A和B都关注的用户列表</li><li><strong>技术选型</strong>：Redis Set集合的交集操作</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="follow-common-2"><p>使用Redis Set数据结构存储关注关系：</p><ul><li><strong>Key设计</strong>：<code>follows:{userId}</code> 存储用户关注的所有人</li><li><strong>集合操作</strong>：<code>SINTER</code>命令求两个集合的交集</li><li><strong>性能优势</strong>：Set操作时间复杂度O(N)，比数据库查询更高效</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="follow-common-3"><p><strong>1. 改造关注逻辑（添加Redis缓存）</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">follow</span><span class="params">(Long followUserId, Boolean isFollow)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.获取登录用户</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;follows:&quot;</span> + userId;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (isFollow) &#123;</span><br><span class="line">        <span class="comment">// 2.关注，新增数据</span></span><br><span class="line">        <span class="type">Follow</span> <span class="variable">follow</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Follow</span>();</span><br><span class="line">        follow.setUserId(userId);</span><br><span class="line">        follow.setFollowUserId(followUserId);</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">isSuccess</span> <span class="operator">=</span> save(follow);</span><br><span class="line">        <span class="keyword">if</span> (isSuccess) &#123;</span><br><span class="line">            <span class="comment">// 把关注用户的id，放入redis的set集合</span></span><br><span class="line">            stringRedisTemplate.opsForSet().add(key, followUserId.toString());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 3.取关，删除数据</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">isSuccess</span> <span class="operator">=</span> remove(<span class="keyword">new</span> <span class="title class_">QueryWrapper</span>&lt;Follow&gt;()</span><br><span class="line">                .eq(<span class="string">&quot;user_id&quot;</span>, userId).eq(<span class="string">&quot;follow_user_id&quot;</span>, followUserId));</span><br><span class="line">        <span class="keyword">if</span> (isSuccess) &#123;</span><br><span class="line">            <span class="comment">// 把关注用户的id从Redis集合中移除</span></span><br><span class="line">            stringRedisTemplate.opsForSet().remove(key, followUserId.toString());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> Result.ok();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>2. 共同关注查询</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">followCommons</span><span class="params">(Long targetUserId)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.获取当前用户</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;follows:&quot;</span> + userId;</span><br><span class="line">    <span class="comment">// 2.求交集</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key2</span> <span class="operator">=</span> <span class="string">&quot;follows:&quot;</span> + targetUserId;</span><br><span class="line">    Set&lt;String&gt; intersect = stringRedisTemplate.opsForSet().intersect(key, key2);</span><br><span class="line">    <span class="keyword">if</span> (intersect == <span class="literal">null</span> || intersect.isEmpty()) &#123;</span><br><span class="line">        <span class="comment">// 无交集</span></span><br><span class="line">        <span class="keyword">return</span> Result.ok(Collections.emptyList());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 3.解析id集合</span></span><br><span class="line">    List&lt;Long&gt; ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());</span><br><span class="line">    <span class="comment">// 4.查询用户详情</span></span><br><span class="line">    List&lt;UserDTO&gt; users = userService.listByIds(ids)</span><br><span class="line">            .stream()</span><br><span class="line">            .map(user -&gt; BeanUtil.copyProperties(user, UserDTO.class))</span><br><span class="line">            .collect(Collectors.toList());</span><br><span class="line">    <span class="keyword">return</span> Result.ok(users);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键点分析</strong></p><ol><li><strong>数据一致性</strong>：数据库和Redis双写一致性，关注成功才写入Redis</li><li><strong>内存优化</strong>：只存储用户ID，不存储完整用户信息</li><li><strong>异常处理</strong>：交集为空时返回空列表，避免空指针异常</li><li><strong>性能考虑</strong>：使用<code>listByIds</code>批量查询，减少数据库访问次数</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="9-3-Feed流实现方案">9.3 Feed流实现方案</h3><p>当我们关注了用户后，这个用户发了动态，那么我们应该把这些数据推送给用户，这个需求，其实我们又把他叫做Feed流，关注推送也叫做Feed流，直译为投喂。为用户持续的提供“沉浸式”的体验，通过无限下拉刷新获取新的信息。</p><p>对于传统的模式的内容解锁：我们是需要用户去通过搜索引擎或者是其他的方式去解锁想要看的内容</p><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653808641260.png" alt="1653808641260"></p><p>对于新型的Feed流的的效果：不需要我们用户再去推送信息，而是系统分析用户到底想要什么，然后直接把内容推送给用户，从而使用户能够更加的节约时间，不用主动去寻找。</p><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653808993693.png" alt="1653808993693"></p><p>Feed流的实现有两种模式：</p><p>Feed流产品有两种常见模式：<br>Timeline：不做内容筛选，简单的按照内容发布时间排序，常用于好友或关注。例如朋友圈</p><ul><li>优点：信息全面，不会有缺失。并且实现也相对简单</li><li>缺点：信息噪音较多，用户不一定感兴趣，内容获取效率低</li></ul><p>智能排序：利用智能算法屏蔽掉违规的、用户不感兴趣的内容。推送用户感兴趣信息来吸引用户</p><ul><li>优点：投喂用户感兴趣信息，用户粘度很高，容易沉迷</li><li>缺点：如果算法不精准，可能起到反作用<br>本例中的个人页面，是基于关注的好友来做Feed流，因此采用Timeline的模式。该模式的实现方案有三种：</li></ul><p>我们本次针对好友的操作，采用的就是Timeline的方式，只需要拿到我们关注用户的信息，然后按照时间排序即可</p><p>，因此采用Timeline的模式。该模式的实现方案有三种：</p><ul><li>拉模式</li><li>推模式</li><li>推拉结合</li></ul><p><strong>拉模式</strong>：也叫做读扩散</p><p>该模式的核心含义就是：当张三和李四和王五发了消息后，都会保存在自己的邮箱中，假设赵六要读取信息，那么他会从读取他自己的收件箱，此时系统会从他关注的人群中，把他关注人的信息全部都进行拉取，然后在进行排序</p><p>优点：比较节约空间，因为赵六在读信息时，并没有重复读取，而且读取完之后可以把他的收件箱进行清楚。</p><p>缺点：比较延迟，当用户读取数据时才去关注的人里边去读取数据，假设用户关注了大量的用户，那么此时就会拉取海量的内容，对服务器压力巨大。</p><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653809450816.png" alt="1653809450816"></p><p><strong>推模式</strong>：也叫做写扩散。</p><p>推模式是没有写邮箱的，当张三写了一个内容，此时会主动的把张三写的内容发送到他的粉丝收件箱中去，假设此时李四再来读取，就不用再去临时拉取了</p><p>优点：时效快，不用临时拉取</p><p>缺点：内存压力大，假设一个大V写信息，很多人关注他， 就会写很多分数据到粉丝那边去</p><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653809875208.png" alt="1653809875208"></p><p><strong>推拉结合模式</strong>：也叫做读写混合，兼具推和拉两种模式的优点。</p><p>发件人端策略：</p><ul><li><p>普通用户：采用&quot;推&quot;模式，直接将消息写入所有粉丝的收件箱（粉丝少，压力小）</p></li><li><p>大V用户：采用&quot;推拉结合&quot;模式，消息先写入自己的发件箱，然后只推送给活跃粉丝<br>收件人端策略：</p></li><li><p>活跃粉丝：无论是大V还是普通用户的消息，都直接推送到收件箱</p></li><li><p>普通粉丝：只在上线时从发件箱中拉取未读消息<br>核心优势：</p></li><li><p>平衡了系统性能和用户体验</p></li><li><p>避免了给所有粉丝推送造成的巨大压力</p></li><li><p>保证了活跃用户的实时性体验</p></li><li><p>降低了存储和计算成本</p></li></ul><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653812346852.png" alt="1653812346852"></p><div class="tabs" id="feed-flow"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#feed-flow-1">需求背景</button></li><li class="tab"><button type="button" data-href="#feed-flow-2">技术方案对比</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="feed-flow-1"><p>Feed流（关注推送）为用户提供沉浸式内容消费体验：</p><ul><li><strong>Timeline模式</strong>：按时间排序，信息全面但噪音较多</li><li><strong>智能排序</strong>：算法推荐，用户粘度高但实现复杂</li><li><strong>本例选择</strong>：基于好友关系的Timeline模式</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="feed-flow-2"><table><thead><tr><th>模式</th><th>原理</th><th>优点</th><th>缺点</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>拉模式</strong></td><td>读取时从关注列表拉取内容</td><td>节省存储空间</td><td>延迟高，压力大</td><td>小用户量</td></tr><tr><td><strong>推模式</strong></td><td>发布时推送到粉丝收件箱</td><td>时效性好</td><td>内存消耗大</td><td>普通用户</td></tr><tr><td><strong>推拉结合</strong></td><td>普通用户推，大V推拉结合</td><td>平衡性能</td><td>实现复杂</td><td>大用户量</td></tr></tbody></table><p><strong>实现选择</strong></p><p>本例采用<strong>推模式</strong>实现：</p><ul><li>用户发布笔记时，主动推送到所有粉丝的收件箱</li><li>使用Redis SortedSet存储，score为时间戳</li><li>支持按时间排序和分页查询</li></ul><p><strong>关键点分析</strong></p><ol><li><strong>数据结构</strong>：<code>ZSet&lt;blogId, timestamp&gt;</code> 天然按时间排序</li><li><strong>写入策略</strong>：发布时同步写入所有粉丝收件箱</li><li><strong>读取优化</strong>：支持范围查询和滚动分页</li><li><strong>存储优化</strong>：只存储blogId，不存储完整内容</li></ol><div class="note flat"><p>推模式适合好友关系场景，时效性好，实现简单。对于大V用户，可后续升级为推拉结合模式。</p></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="9-4-推送到粉丝收件箱">9.4 推送到粉丝收件箱</h3><div class="tabs" id="feed-push"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#feed-push-1">需求分析</button></li><li class="tab"><button type="button" data-href="#feed-push-2">技术方案</button></li><li class="tab"><button type="button" data-href="#feed-push-3">代码实现</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="feed-push-1"><p>实现笔记发布时的粉丝推送功能：</p><ul><li><strong>触发时机</strong>：用户发布探店笔记时</li><li><strong>推送对象</strong>：笔记作者的所有粉丝</li><li><strong>存储要求</strong>：按时间戳排序，支持分页查询</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="feed-push-2"><p>使用Redis SortedSet作为粉丝收件箱：</p><ul><li><strong>Key格式</strong>：<code>feed:{userId}</code> 存储用户的收件箱</li><li><strong>Value格式</strong>：<code>ZSet&lt;blogId, timestamp&gt;</code></li><li><strong>推送逻辑</strong>：遍历所有粉丝，写入对应收件箱</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="feed-push-3"><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">saveBlog</span><span class="params">(Blog blog)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.获取登录用户</span></span><br><span class="line">    <span class="type">UserDTO</span> <span class="variable">user</span> <span class="operator">=</span> UserHolder.getUser();</span><br><span class="line">    blog.setUserId(user.getId());</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2.保存探店笔记到数据库</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">isSuccess</span> <span class="operator">=</span> save(blog);</span><br><span class="line">    <span class="keyword">if</span>(!isSuccess)&#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;新增笔记失败!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3.查询笔记作者的所有粉丝</span></span><br><span class="line">    List&lt;Follow&gt; follows = followService.query()</span><br><span class="line">            .eq(<span class="string">&quot;follow_user_id&quot;</span>, user.getId()).list();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4.推送笔记id给所有粉丝</span></span><br><span class="line">    <span class="keyword">for</span> (Follow follow : follows) &#123;</span><br><span class="line">        <span class="comment">// 4.1.获取粉丝id</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> follow.getUserId();</span><br><span class="line">        <span class="comment">// 4.2.推送</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> FEED_KEY + userId;</span><br><span class="line">        stringRedisTemplate.opsForZSet()</span><br><span class="line">                .add(key, blog.getId().toString(), System.currentTimeMillis());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 5.返回笔记id</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok(blog.getId());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键点分析</strong></p><ol><li><strong>原子性</strong>：数据库保存成功后才进行推送</li><li><strong>批量操作</strong>：遍历所有粉丝，逐个写入收件箱</li><li><strong>时间戳</strong>：使用当前时间作为score，保证时间排序</li><li><strong>异常处理</strong>：任一粉丝推送失败不影响其他粉丝</li></ol><div class="note flat"><p>推模式的关键是数据一致性，确保数据库保存成功后才进行Redis推送。</p></div><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">saveBlog</span><span class="params">(Blog blog)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.获取登录用户</span></span><br><span class="line">    <span class="type">UserDTO</span> <span class="variable">user</span> <span class="operator">=</span> UserHolder.getUser();</span><br><span class="line">    blog.setUserId(user.getId());</span><br><span class="line">    <span class="comment">// 2.保存探店笔记</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">isSuccess</span> <span class="operator">=</span> save(blog);</span><br><span class="line">    <span class="keyword">if</span>(!isSuccess)&#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="string">&quot;新增笔记失败!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 3.查询笔记作者的所有粉丝 select * from tb_follow where follow_user_id = ?</span></span><br><span class="line">    List&lt;Follow&gt; follows = followService.query().eq(<span class="string">&quot;follow_user_id&quot;</span>, user.getId()).list();</span><br><span class="line">    <span class="comment">// 4.推送笔记id给所有粉丝</span></span><br><span class="line">    <span class="keyword">for</span> (Follow follow : follows) &#123;</span><br><span class="line">        <span class="comment">// 4.1.获取粉丝id</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> follow.getUserId();</span><br><span class="line">        <span class="comment">// 4.2.推送</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> FEED_KEY + userId;</span><br><span class="line">        stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 5.返回id</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok(blog.getId());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="9-5-实现分页查询收件箱">9.5 实现分页查询收件箱</h3><p>需求：在个人主页的“关注”卡片中，查询并展示推送的Blog信息：</p><div class="tabs" id="feed-query"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#feed-query-1">需求分析</button></li><li class="tab"><button type="button" data-href="#feed-query-2">技术方案</button></li><li class="tab"><button type="button" data-href="#feed-query-3">代码实现</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="feed-query-1"><p>Feed流分页查询面临特殊挑战：</p><ul><li><strong>数据动态性</strong>：新数据不断插入，传统分页会重复读取</li><li><strong>滚动分页</strong>：基于时间戳和偏移量实现无重复分页</li><li><strong>性能要求</strong>：需要高效的范围查询和排序</li></ul><p><strong>1 传统分页</strong></p><p>传统了分页在feed流是不适用的，因为我们的数据会随时发生变化</p><p>假设在t1 时刻，我们去读取第一页，此时page = 1 ，size = 5 ，那么我们拿到的就是10~6 这几条记录，假设现在t2时候又发布了一条记录，此时t3 时刻，我们来读取第二页，读取第二页传入的参数是page=2 ，size=5 ，那么此时读取到的第二页实际上是从6 开始，然后是6~2 ，那么我们就读取到了重复的数据，所以feed流的分页，不能采用原始方案来做。</p><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653813047671.png" alt="1653813047671"></p><p><strong>2 Feed流的滚动分页</strong></p><p>我们需要记录每次操作的最后一条，然后从这个位置开始去读取数据</p><p>举个例子：我们从t1时刻开始，拿第一页数据，拿到了10~6，然后记录下当前最后一次拿取的记录，就是6，t2时刻发布了新的记录，此时这个11放到最顶上，但是不会影响我们之前记录的6，此时t3时刻来拿第二页，第二页这个时候拿数据，还是从6后一点的5去拿，就拿到了5-1的记录。我们这个地方可以采用sortedSet来做，可以进行范围查询，并且还可以记录当前获取数据时间戳最小值，就可以实现滚动分页了</p><p><img src="https://markpic.adoreorg.cn/2025/09/Redis/1653813462834.png" alt="1653813462834"></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="feed-query-2"><p>使用Redis SortedSet的滚动分页：</p><ul><li><strong>查询命令</strong>：<code>ZREVRANGEBYSCORE key Max Min LIMIT offset count</code></li><li><strong>分页参数</strong>：max(最大时间戳)、offset(偏移量)、count(每页数量)</li><li><strong>返回数据</strong>：blogId列表、最小时间戳、新偏移量</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="feed-query-3"><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Controller层</span></span><br><span class="line"><span class="meta">@GetMapping(&quot;/of/follow&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryBlogOfFollow</span><span class="params">(</span></span><br><span class="line"><span class="params">    <span class="meta">@RequestParam(&quot;lastId&quot;)</span> Long max, </span></span><br><span class="line"><span class="params">    <span class="meta">@RequestParam(value = &quot;offset&quot;, defaultValue = &quot;0&quot;)</span> Integer offset)</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> blogService.queryBlogOfFollow(max, offset);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Service层</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryBlogOfFollow</span><span class="params">(Long max, Integer offset)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.获取当前用户</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2.查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> FEED_KEY + userId;</span><br><span class="line">    Set&lt;ZSetOperations.TypedTuple&lt;String&gt;&gt; typedTuples = stringRedisTemplate.opsForZSet()</span><br><span class="line">        .reverseRangeByScoreWithScores(key, <span class="number">0</span>, max, offset, <span class="number">2</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3.非空判断</span></span><br><span class="line">    <span class="keyword">if</span> (typedTuples == <span class="literal">null</span> || typedTuples.isEmpty()) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.ok();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4.解析数据：blogId、minTime（时间戳）、offset</span></span><br><span class="line">    List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(typedTuples.size());</span><br><span class="line">    <span class="type">long</span> <span class="variable">minTime</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">os</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (ZSetOperations.TypedTuple&lt;String&gt; tuple : typedTuples) &#123;</span><br><span class="line">        <span class="comment">// 4.1.获取id</span></span><br><span class="line">        ids.add(Long.valueOf(tuple.getValue()));</span><br><span class="line">        <span class="comment">// 4.2.获取分数(时间戳）</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">time</span> <span class="operator">=</span> tuple.getScore().longValue();</span><br><span class="line">        <span class="keyword">if</span>(time == minTime)&#123;</span><br><span class="line">            os++;  <span class="comment">// 相同时间戳，偏移量+1</span></span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            minTime = time;  <span class="comment">// 更新时间戳</span></span><br><span class="line">            os = <span class="number">1</span>;  <span class="comment">// 重置偏移量</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 5.根据id查询blog</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">idStr</span> <span class="operator">=</span> StrUtil.join(<span class="string">&quot;,&quot;</span>, ids);</span><br><span class="line">    List&lt;Blog&gt; blogs = query().in(<span class="string">&quot;id&quot;</span>, ids).last(<span class="string">&quot;ORDER BY FIELD(id,&quot;</span> + idStr + <span class="string">&quot;)&quot;</span>).list();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (Blog blog : blogs) &#123;</span><br><span class="line">        <span class="comment">// 5.1.查询blog有关的用户</span></span><br><span class="line">        queryBlogUser(blog);</span><br><span class="line">        <span class="comment">// 5.2.查询blog是否被点赞</span></span><br><span class="line">        isBlogLiked(blog);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 6.封装并返回</span></span><br><span class="line">    <span class="type">ScrollResult</span> <span class="variable">r</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ScrollResult</span>();</span><br><span class="line">    r.setList(blogs);</span><br><span class="line">    r.setOffset(os);</span><br><span class="line">    r.setMinTime(minTime);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> Result.ok(r);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键点分析</strong></p><ol><li><strong>滚动分页</strong>：基于时间戳和偏移量，避免重复数据</li><li><strong>ZSet查询</strong>：<code>reverseRangeByScoreWithScores</code> 支持范围查询</li><li><strong>偏移量计算</strong>：处理相同时间戳的多条数据</li><li><strong>数据完整性</strong>：查询blog详情和点赞状态</li></ol><div class="note flat"><p>滚动分页的核心是记录上次查询的最小时间戳和偏移量，下次查询从该位置继续。</p></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="10-附近商户">10. 附近商户</h2><h3 id="10-1-GEO数据结构基本用法">10.1 GEO数据结构基本用法</h3><div class="tabs" id="geo-basic"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#geo-basic-1">功能概述</button></li><li class="tab"><button type="button" data-href="#geo-basic-2">应用场景</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="geo-basic-1"><p><strong>功能概述</strong>：存储和查询地理坐标信息</p><p>Redis GEO（地理坐标）支持存储和查询地理坐标信息：</p><ul><li><strong>坐标存储</strong>：经度(longitude)、纬度(latitude)、成员(member)</li><li><strong>距离计算</strong>：两点间的球面距离 单位 m-米，km-千米，mi-英⾥，ft-英尺</li><li><strong>范围查询</strong>：圆形或矩形范围内的成员</li><li><strong>排序返回</strong>：按距离排序查询结果</li></ul><p><strong>常用命令</strong></p><table><thead><tr><th>命令</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td><code>GEOADD</code></td><td>添加地理坐标</td><td><code>GEOADD cities 116.405 39.905 beijing</code></td></tr><tr><td><code>GEODIST</code></td><td>计算两点距离</td><td><code>GEODIST cities beijing shanghai km</code></td></tr><tr><td><code>GEOPOS</code></td><td>获取成员坐标</td><td><code>GEOPOS cities beijing</code></td></tr><tr><td><code>GEOSEARCH</code></td><td>范围搜索排序</td><td><code>GEOSEARCH cities FROMLONLAT 116 39 BYRADIUS 10 km</code></td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="geo-basic-2"><p><strong>应用场景</strong></p><ul><li><strong>附近商户搜索</strong>：根据用户当前位置，查询距离最近的商户</li><li><strong>地理位置服务</strong>：提供基于位置的服务，如地图导航、位置提醒</li><li><strong>距离计算和排序</strong>：计算两点之间的距离，支持按距离排序查询结果</li><li><strong>基于位置的内容推荐</strong>：根据用户位置推荐相关内容，如本地美食、景点等</li></ul><p><strong>关键点分析</strong></p><ol><li><strong>坐标精度</strong>：支持小数点后6位，约10cm精度</li><li><strong>距离单位</strong>：支持m、km、mi、ft等单位</li><li><strong>范围查询</strong>：支持圆形和矩形两种查询方式</li><li><strong>性能优化</strong>：使用geohash编码，查询效率高</li></ol><div class="note flat"><p>Redis 6.2+ 推荐使用 <code>GEOSEARCH</code> 命令，功能更强大，支持更复杂的查询条件。</p></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="10-2-导入店铺数据到GEO">10.2 导入店铺数据到GEO</h3><div class="tabs" id="geo-import"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#geo-import-1">需求分析</button></li><li class="tab"><button type="button" data-href="#geo-import-2">代码实现</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="geo-import-1"><p><strong>需求分析</strong></p><p>将数据库中的店铺信息导入Redis GEO，支持按类型分类存储：</p><ul><li><strong>数据分组</strong>：按店铺类型分组，同类店铺存储在同一key下</li><li><strong>坐标存储</strong>：使用店铺ID作为member，经纬度作为坐标</li><li><strong>批量导入</strong>：提高导入效率，减少网络开销</li></ul><p><strong>技术方案</strong></p><p>使用Redis GEOADD命令批量导入：</p><ul><li><strong>Key格式</strong>：<code>shop:geo:{typeId}</code> 按店铺类型分组</li><li><strong>Value格式</strong>：<code>GeoLocation&lt;shopId, Point(x,y)&gt;</code></li><li><strong>批量操作</strong>：<code>opsForGeo().add(key, locations)</code> 一次性导入</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="geo-import-2"><p><strong>代码实现</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">loadShopData</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 1.查询店铺信息</span></span><br><span class="line">    List&lt;Shop&gt; list = shopService.list();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2.把店铺分组，按照typeId分组</span></span><br><span class="line">    Map&lt;Long, List&lt;Shop&gt;&gt; map = list.stream()</span><br><span class="line">        .collect(Collectors.groupingBy(Shop::getTypeId));</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3.分批完成写入Redis</span></span><br><span class="line">    <span class="keyword">for</span> (Map.Entry&lt;Long, List&lt;Shop&gt;&gt; entry : map.entrySet()) &#123;</span><br><span class="line">        <span class="comment">// 3.1.获取类型id</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">typeId</span> <span class="operator">=</span> entry.getKey();</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> SHOP_GEO_KEY + typeId;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3.2.获取同类型的店铺的集合</span></span><br><span class="line">        List&lt;Shop&gt; value = entry.getValue();</span><br><span class="line">        List&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt; locations = </span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(value.size());</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3.3.构建GeoLocation对象</span></span><br><span class="line">        <span class="keyword">for</span> (Shop shop : value) &#123;</span><br><span class="line">            locations.add(<span class="keyword">new</span> <span class="title class_">RedisGeoCommands</span>.GeoLocation&lt;&gt;(</span><br><span class="line">                shop.getId().toString(),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">Point</span>(shop.getX(), shop.getY())</span><br><span class="line">            ));</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3.4.批量写入redis GEOADD key 经度 纬度 member</span></span><br><span class="line">        stringRedisTemplate.opsForGeo().add(key, locations);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键点分析</strong></p><ol><li><strong>数据分组</strong>：按typeId分组，支持按类型筛选</li><li><strong>批量导入</strong>：减少网络请求，提高导入效率</li><li><strong>内存优化</strong>：只存储shopId，不存储完整信息</li><li><strong>坐标格式</strong>：使用RedisGeoCommands.GeoLocation封装</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="10-3-实现附近商户功能">10.3 实现附近商户功能</h3><div class="tabs" id="geo-search"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#geo-search-1">需求分析</button></li><li class="tab"><button type="button" data-href="#geo-search-2">代码实现</button></li><li class="tab"><button type="button" data-href="#geo-search-3">发送请求</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="geo-search-1"><p><strong>需求分析</strong></p><p>实现基于地理位置的商户查询功能：</p><ul><li><strong>坐标查询</strong>：根据用户当前位置查询附近商户</li><li><strong>距离排序</strong>：按距离从近到远排序</li><li><strong>分页支持</strong>：支持分页查询，避免一次性返回大量数据</li><li><strong>类型筛选</strong>：可按商户类型筛选结果</li></ul><p><strong>技术方案</strong></p><p>使用Redis GEOSEARCH命令实现：</p><ul><li><strong>查询命令</strong>：<code>GEOSEARCH key BYLONLAT x y BYRADIUS radius WITHDISTANCE</code></li><li><strong>分页处理</strong>：先查询足够数据，再进行内存分页</li><li><strong>距离计算</strong>：自动计算并返回每个商户的距离</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="geo-search-2"><p><strong>引入依赖</strong><br><strong>SpringDataRedis的2.3.9版本并不支持Redis 6.2提供的GEOSEARCH命令，因此我们需要提示其版本，修改自己的pom.xml文件</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;</span><br><span class="line">    &lt;exclusions&gt;</span><br><span class="line">        &lt;exclusion&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-data-redis&lt;/artifactId&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.data&lt;/groupId&gt;</span><br><span class="line">        &lt;/exclusion&gt;</span><br><span class="line">        &lt;exclusion&gt;</span><br><span class="line">            &lt;artifactId&gt;lettuce-core&lt;/artifactId&gt;</span><br><span class="line">            &lt;groupId&gt;io.lettuce&lt;/groupId&gt;</span><br><span class="line">        &lt;/exclusion&gt;</span><br><span class="line">    &lt;/exclusions&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.data&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-data-redis&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;<span class="number">2.6</span><span class="number">.2</span>&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;io.lettuce&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;lettuce-core&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;<span class="number">6.1</span><span class="number">.6</span>.RELEASE&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure><p><strong>代码实现</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Controller层</span></span><br><span class="line"><span class="meta">@GetMapping(&quot;/of/type&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryShopByType</span><span class="params">(</span></span><br><span class="line"><span class="params">        <span class="meta">@RequestParam(&quot;typeId&quot;)</span> Integer typeId,</span></span><br><span class="line"><span class="params">        <span class="meta">@RequestParam(value = &quot;current&quot;, defaultValue = &quot;1&quot;)</span> Integer current,</span></span><br><span class="line"><span class="params">        <span class="meta">@RequestParam(value = &quot;x&quot;, required = false)</span> Double x,</span></span><br><span class="line"><span class="params">        <span class="meta">@RequestParam(value = &quot;y&quot;, required = false)</span> Double y</span></span><br><span class="line"><span class="params">)</span> &#123;</span><br><span class="line">   <span class="keyword">return</span> shopService.queryShopByType(typeId, current, x, y);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Service层</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">queryShopByType</span><span class="params">(Integer typeId, Integer current, Double x, Double y)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.判断是否需要根据坐标查询</span></span><br><span class="line">    <span class="keyword">if</span> (x == <span class="literal">null</span> || y == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 不需要坐标查询，按数据库查询</span></span><br><span class="line">        Page&lt;Shop&gt; page = query()</span><br><span class="line">                .eq(<span class="string">&quot;type_id&quot;</span>, typeId)</span><br><span class="line">                .page(<span class="keyword">new</span> <span class="title class_">Page</span>&lt;&gt;(current, SystemConstants.DEFAULT_PAGE_SIZE));</span><br><span class="line">        <span class="keyword">return</span> Result.ok(page.getRecords());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 2.计算分页参数</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">from</span> <span class="operator">=</span> (current - <span class="number">1</span>) * SystemConstants.DEFAULT_PAGE_SIZE;</span><br><span class="line">    <span class="type">int</span> <span class="variable">end</span> <span class="operator">=</span> current * SystemConstants.DEFAULT_PAGE_SIZE;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 3.查询redis、按照距离排序、分页</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> SHOP_GEO_KEY + typeId;</span><br><span class="line">    GeoResults&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt; results = stringRedisTemplate.opsForGeo()</span><br><span class="line">            .search(</span><br><span class="line">                    key,</span><br><span class="line">                    GeoReference.fromCoordinate(x, y),</span><br><span class="line">                    <span class="keyword">new</span> <span class="title class_">Distance</span>(<span class="number">5000</span>), <span class="comment">// 5km范围内</span></span><br><span class="line">                    RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs()</span><br><span class="line">                            .includeDistance()</span><br><span class="line">                            .limit(end)</span><br><span class="line">            );</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4.解析出id和距离</span></span><br><span class="line">    <span class="keyword">if</span> (results == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.ok(Collections.emptyList());</span><br><span class="line">    &#125;</span><br><span class="line">    List&lt;GeoResult&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt;&gt; list = results.getContent();</span><br><span class="line">    <span class="keyword">if</span> (list.size() &lt;= from) &#123;</span><br><span class="line">        <span class="comment">// 没有下一页了，结束</span></span><br><span class="line">        <span class="keyword">return</span> Result.ok(Collections.emptyList());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4.1.截取 from ~ end的部分</span></span><br><span class="line">    List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(list.size());</span><br><span class="line">    Map&lt;String, Distance&gt; distanceMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(list.size());</span><br><span class="line">    list.stream().skip(from).forEach(result -&gt; &#123;</span><br><span class="line">        <span class="comment">// 4.2.获取店铺id</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">shopIdStr</span> <span class="operator">=</span> result.getContent().getName();</span><br><span class="line">        ids.add(Long.valueOf(shopIdStr));</span><br><span class="line">        <span class="comment">// 4.3.获取距离</span></span><br><span class="line">        <span class="type">Distance</span> <span class="variable">distance</span> <span class="operator">=</span> result.getDistance();</span><br><span class="line">        distanceMap.put(shopIdStr, distance);</span><br><span class="line">    &#125;);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 5.根据id查询Shop详情</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">idStr</span> <span class="operator">=</span> StrUtil.join(<span class="string">&quot;,&quot;</span>, ids);</span><br><span class="line">    List&lt;Shop&gt; shops = query().in(<span class="string">&quot;id&quot;</span>, ids).last(<span class="string">&quot;ORDER BY FIELD(id,&quot;</span> + idStr + <span class="string">&quot;)&quot;</span>).list();</span><br><span class="line">    <span class="keyword">for</span> (Shop shop : shops) &#123;</span><br><span class="line">        shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 6.返回</span></span><br><span class="line">    <span class="keyword">return</span> Result.ok(shops);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键点分析</strong></p><ol><li><strong>坐标判断</strong>：无坐标时回退到数据库查询</li><li><strong>分页计算</strong>：内存分页，避免数据库分页的排序问题</li><li><strong>范围查询</strong>：5km范围内搜索，避免数据量过大</li><li><strong>距离设置</strong>：自动将距离信息设置到店铺对象中</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="geo-search-3"><p><strong>请求网址: <a href="http://localhost:8080/api/shop/of/type?typeId=1&amp;current=1&amp;x=120.149993&amp;y=30.334229">http://localhost:8080/api/shop/of/type?typeId=1&amp;current=1&amp;x=120.149993&amp;y=30.334229</a></strong><br><strong>请求方法: GET</strong></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="11-用户签到">11. 用户签到</h2><h3 id="11-1-BitMap功能演示">11.1 BitMap功能演示</h3><div class="tabs" id="bitmap-basic"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#bitmap-basic-1">功能概述</button></li><li class="tab"><button type="button" data-href="#bitmap-basic-2">应用场景</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="bitmap-basic-1"><p><strong>功能概述</strong></p><p>BitMap是Redis中基于String类型实现的位图数据结构：</p><ul><li><strong>存储原理</strong>：每个bit位代表一个状态，0表示未签到，1表示已签到</li><li><strong>空间优势</strong>：相比MySQL存储，内存占用减少99%以上</li><li><strong>性能优势</strong>：位操作时间复杂度为O(1)，查询效率极高</li></ul><p><strong>常用命令</strong></p><table><thead><tr><th>命令</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td>SETBIT</td><td>设置指定位置的bit值</td><td><code>SETBIT sign:1:202401 15 1</code></td></tr><tr><td>GETBIT</td><td>获取指定位置的bit值</td><td><code>GETBIT sign:1:202401 15</code></td></tr><tr><td>BITCOUNT</td><td>统计1的个数</td><td><code>BITCOUNT sign:1:202401</code></td></tr><tr><td>BITFIELD</td><td>批量操作bit位</td><td><code>BITFIELD sign:1:202401 GET u31 0</code></td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="bitmap-basic-2"><p><strong>应用场景</strong></p><ul><li><strong>用户签到</strong>：按月存储用户签到状态</li><li><strong>活跃用户统计</strong>：统计某时间段活跃用户</li><li><strong>特征标记</strong>：标记用户是否具有某种特征</li><li><strong>布隆过滤器</strong>：快速判断元素是否存在</li></ul><p><strong>关键点分析</strong></p><ol><li><strong>存储上限</strong>：最大支持512MB，约43亿个bit位</li><li><strong>时间效率</strong>：位操作都是O(1)时间复杂度</li><li><strong>空间效率</strong>：1亿用户10次签到仅需12MB内存</li><li><strong>数据格式</strong>：按年+月分组存储，便于统计</li></ol><div class="note info simple"><p>BitMap适合存储大量布尔值状态，内存占用极小，查询效率极高。</p></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="11-2-实现签到功能">11.2 实现签到功能</h3><div class="tabs" id="sign-save"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#sign-save-1">需求分析</button></li><li class="tab"><button type="button" data-href="#sign-save-2">代码实现</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="sign-save-1"><p><strong>需求分析</strong></p><p>实现用户签到功能，将当天签到信息保存到Redis中：</p><ul><li><strong>签到记录</strong>：记录用户每天的签到状态</li><li><strong>状态存储</strong>：使用BitMap存储，0表示未签到，1表示已签到</li><li><strong>时间维度</strong>：按月维度存储，便于统计和查询</li><li><strong>幂等性</strong>：同一天多次签到只记录一次</li></ul><p><strong>技术方案</strong></p><p>使用Redis BitMap实现签到存储：</p><ul><li><strong>Key格式</strong>：<code>sign:userId:yyyyMM</code></li><li><strong>Bit位置</strong>：用月份中的第几天作为offset（0-30）</li><li><strong>命令选择</strong>：使用SETBIT命令设置签到状态</li><li><strong>时间获取</strong>：通过后台代码获取当前日期</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="sign-save-2"><p><strong>代码实现</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Controller层</span></span><br><span class="line"><span class="meta">@PostMapping(&quot;/sign&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">sign</span><span class="params">()</span>&#123;</span><br><span class="line">   <span class="keyword">return</span> userService.sign();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Service层</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">sign</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 1.获取当前登录用户</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="comment">// 2.获取当前日期</span></span><br><span class="line">    <span class="type">LocalDateTime</span> <span class="variable">now</span> <span class="operator">=</span> LocalDateTime.now();</span><br><span class="line">    <span class="comment">// 3.拼接key：sign:userId:yyyyMM</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">keySuffix</span> <span class="operator">=</span> now.format(DateTimeFormatter.ofPattern(<span class="string">&quot;:yyyyMM&quot;</span>));</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> USER_SIGN_KEY + userId + keySuffix;</span><br><span class="line">    <span class="comment">// 4.获取今天是本月的第几天（1-31）</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">dayOfMonth</span> <span class="operator">=</span> now.getDayOfMonth();</span><br><span class="line">    <span class="comment">// 5.写入Redis：SETBIT key offset 1</span></span><br><span class="line">    stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - <span class="number">1</span>, <span class="literal">true</span>);</span><br><span class="line">    <span class="keyword">return</span> Result.ok();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键点分析</strong></p><ol><li><strong>Key设计</strong>：按用户和月份维度存储，便于查询和统计</li><li><strong>Offset计算</strong>：使用月份中的第几天减1作为bit位偏移</li><li><strong>幂等性保证</strong>：SETBIT命令天然幂等，重复签到无影响</li><li><strong>时间处理</strong>：通过LocalDateTime获取准确的日期信息</li></ol><div class="note success simple"><p>BitMap的SETBIT命令天然幂等，同一天多次签到不会产生重复记录。</p></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="11-3-签到统计">11.3 签到统计</h3><div class="tabs" id="sign-count"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#sign-count-1">需求分析</button></li><li class="tab"><button type="button" data-href="#sign-count-2">代码实现</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="sign-count-1"><p><strong>需求分析</strong></p><p>统计用户本月的连续签到天数：</p><ul><li><strong>连续签到定义</strong>：从最后一次签到开始向前统计，直到遇到第一次未签到为止</li><li><strong>统计范围</strong>：本月第一天到当前日期</li><li><strong>返回结果</strong>：连续签到天数</li><li><strong>算法思路</strong>：从后向前遍历bit位，遇到0停止</li></ul><p><strong>技术方案</strong></p><p>使用BITFIELD命令获取本月签到数据：</p><ul><li><strong>命令格式</strong>：<code>BITFIELD key GET u[dayOfMonth] 0</code></li><li><strong>数据处理</strong>：获取本月所有签到记录，返回十进制数字</li><li><strong>位运算</strong>：通过与1进行与运算，逐个检查bit位</li><li><strong>遍历逻辑</strong>：从后向前遍历，遇到0停止计数</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="sign-count-2"><p><strong>代码实现</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Controller层</span></span><br><span class="line"><span class="meta">@GetMapping(&quot;/sign/count&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">signCount</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> userService.signCount();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Service层</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">signCount</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 1.获取当前登录用户</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">userId</span> <span class="operator">=</span> UserHolder.getUser().getId();</span><br><span class="line">    <span class="comment">// 2.获取当前日期</span></span><br><span class="line">    <span class="type">LocalDateTime</span> <span class="variable">now</span> <span class="operator">=</span> LocalDateTime.now();</span><br><span class="line">    <span class="comment">// 3.拼接key：sign:userId:yyyyMM</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">keySuffix</span> <span class="operator">=</span> now.format(DateTimeFormatter.ofPattern(<span class="string">&quot;:yyyyMM&quot;</span>));</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> USER_SIGN_KEY + userId + keySuffix;</span><br><span class="line">    <span class="comment">// 4.获取今天是本月的第几天</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">dayOfMonth</span> <span class="operator">=</span> now.getDayOfMonth();</span><br><span class="line">    <span class="comment">// 5.获取本月截止今天为止的所有签到记录</span></span><br><span class="line">    <span class="comment">// BITFIELD sign:1:202401 GET u31 0</span></span><br><span class="line">    List&lt;Long&gt; result = stringRedisTemplate.opsForValue().bitField(</span><br><span class="line">            key,</span><br><span class="line">            BitFieldSubCommands.create()</span><br><span class="line">                    .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(<span class="number">0</span>)</span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">if</span> (result == <span class="literal">null</span> || result.isEmpty()) &#123;</span><br><span class="line">        <span class="comment">// 没有任何签到结果</span></span><br><span class="line">        <span class="keyword">return</span> Result.ok(<span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">Long</span> <span class="variable">num</span> <span class="operator">=</span> result.get(<span class="number">0</span>);</span><br><span class="line">    <span class="keyword">if</span> (num == <span class="literal">null</span> || num == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.ok(<span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 6.循环遍历统计连续签到天数</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">        <span class="comment">// 6.1.让这个数字与1做与运算，得到数字的最后一个bit位</span></span><br><span class="line">        <span class="keyword">if</span> ((num &amp; <span class="number">1</span>) == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 如果为0，说明未签到，结束统计</span></span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 如果不为0，说明已签到，计数器+1</span></span><br><span class="line">            count++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 把数字右移一位，抛弃最后一个bit位，继续下一个bit位</span></span><br><span class="line">        num &gt;&gt;&gt;= <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> Result.ok(count);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键点分析</strong></p><ol><li><strong>连续签到定义</strong>：从最后一次签到向前统计，遇到0停止</li><li><strong>BITFIELD使用</strong>：一次性获取本月所有签到数据</li><li><strong>位运算技巧</strong>：通过与1进行与运算判断最低位是否为1</li><li><strong>遍历方向</strong>：从后向前遍历，符合连续签到定义</li></ol><div class="note info simple"><p>使用无符号右移（&gt;&gt;&gt;&quot;)确保高位补0，避免负数影响统计结果。</p></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="11-4-BitMap解决缓存穿透">11.4 BitMap解决缓存穿透</h3><div class="tabs" id="bitmap-bloom"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#bitmap-bloom-1">需求分析</button></li><li class="tab"><button type="button" data-href="#bitmap-bloom-2">代码实现</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="bitmap-bloom-1"><p><strong>需求分析</strong></p><p>使用BitMap解决缓存穿透问题：</p><ul><li><strong>缓存穿透</strong>：查询不存在的数据，绕过缓存直达数据库</li><li><strong>传统方案</strong>：使用list存储所有有效id，内存占用大</li><li><strong>优化方案</strong>：使用BitMap作为布隆过滤器，快速判断id是否存在</li><li><strong>误差控制</strong>：通过哈希算法降低冲突概率</li></ul><p><strong>技术方案</strong></p><p>基于BitMap实现布隆过滤器：</p><ul><li><strong>哈希算法</strong>：id % bitmap_size，确定bit位置</li><li><strong>存储方式</strong>：将数据库中所有有效id对应的bit位置为1</li><li><strong>查询逻辑</strong>：用户查询时，用相同算法计算bit位，为0则一定不存在</li><li><strong>误差处理</strong>：哈希冲突可能导致误判，但概率可控</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="bitmap-bloom-2"><p><strong>代码实现</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 初始化布隆过滤器</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">initBloomFilter</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 1.获取数据库中所有有效id</span></span><br><span class="line">    List&lt;Long&gt; validIds = getAllValidIdsFromDB();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2.计算bitmap大小（根据数据量和期望误差率）</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">bitmapSize</span> <span class="operator">=</span> calculateOptimalSize(validIds.size(), <span class="number">0.01</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3.将每个id映射到bit位并设置</span></span><br><span class="line">    <span class="keyword">for</span> (Long id : validIds) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">bitIndex</span> <span class="operator">=</span> (<span class="type">int</span>) (id % bitmapSize);</span><br><span class="line">        stringRedisTemplate.opsForValue().setBit(<span class="string">&quot;bloom:filter&quot;</span>, bitIndex, <span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 查询时判断</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">mightExist</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">bitmapSize</span> <span class="operator">=</span> getBitmapSize(); <span class="comment">// 获取之前计算的size</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">bitIndex</span> <span class="operator">=</span> (<span class="type">int</span>) (id % bitmapSize);</span><br><span class="line">    <span class="keyword">return</span> stringRedisTemplate.opsForValue().getBit(<span class="string">&quot;bloom:filter&quot;</span>, bitIndex);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键点分析</strong></p><ol><li><strong>内存优化</strong>：相比存储完整id列表，内存占用减少99%以上</li><li><strong>查询效率</strong>：位操作时间复杂度O(1)，查询极快</li><li><strong>误差控制</strong>：通过合理设置bitmap大小，可将误差率控制在1%以内</li><li><strong>数据更新</strong>：数据库新增/删除数据时需要同步更新bitmap</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="12-UV统计">12. UV统计</h2><h3 id="12-1-HyperLogLog原理">12.1 HyperLogLog原理</h3><div class="tabs" id="hyperloglog-basic"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#hyperloglog-basic-1">概念定义</button></li><li class="tab"><button type="button" data-href="#hyperloglog-basic-2">应用场景</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="hyperloglog-basic-1"><p><strong>概念定义</strong></p><ul><li><strong>UV（Unique Visitor）</strong>：独立访客量，同一用户多次访问只计1次</li><li><strong>PV（Page View）</strong>：页面访问量，每次访问都计数</li><li><strong>统计挑战</strong>：UV需要去重，传统Set存储方式内存消耗巨大</li></ul><p><strong>HyperLogLog原理</strong></p><div class='liushen-tag-link'><a class="tag-Link" target="_blank" href=" https://juejin.cn/post/6844903785744056333#heading-0">    <div class="tag-link-tips">🪧引用站外地址，不保证站点的可用性和安全性</div>    <div class="tag-link-bottom">        <div class="tag-link-left" style="background-image: url(https://source.adoreorg.cn/webp/icon/66a4632bbf06e.webp);"></div>        <div class="tag-link-right">            <div class="tag-link-title">HyperLogLog原理</div>            <div class="tag-link-sitename"> https://source.adoreorg.cn/webp/icon/66a4632bbf06e.webp</div>        </div>        <i class="fa-solid fa-angle-right"></i>    </div>    </a></div><p>HyperLogLog是基于概率算法的基数统计方案：</p><ul><li><strong>内存占用</strong>：单个HLL结构小于16KB，与数据量无关</li><li><strong>误差范围</strong>：标准误差小于0.81%，对UV统计可接受</li><li><strong>底层实现</strong>：基于String结构，使用位运算和哈希算法</li><li><strong>适用场景</strong>：大数据量去重统计，如UV、DAU等</li></ul><p><strong>Redis命令</strong></p><table><thead><tr><th>命令</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td>PFADD</td><td>添加元素</td><td><code>PFADD uv:202401 user1 user2</code></td></tr><tr><td>PFCOUNT</td><td>统计基数</td><td><code>PFCOUNT uv:202401</code></td></tr><tr><td>PFMERGE</td><td>合并多个HLL</td><td><code>PFMERGE uv:total uv:202401 uv:202402</code></td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="hyperloglog-basic-2"><p><strong>应用场景</strong></p><ul><li><strong>网站UV统计</strong>：统计每日/月独立访客</li><li><strong>APP日活统计</strong>：统计每日活跃用户</li><li><strong>广告点击统计</strong>：统计独立点击用户数</li><li><strong>用户行为分析</strong>：统计参与特定活动的用户数</li></ul><p><strong>关键点分析</strong></p><ol><li><strong>内存优势</strong>：相比Set存储，内存节省99%以上</li><li><strong>误差可控</strong>：0.81%误差对大多数业务场景可接受</li><li><strong>合并支持</strong>：支持多时间段数据合并统计</li><li><strong>性能高效</strong>：添加和查询操作都是O(1)复杂度</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="12-2-百万级数据统计测试">12.2 百万级数据统计测试</h3><div class="tabs" id="hyperloglog-test"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#hyperloglog-test-1">测试目标</button></li><li class="tab"><button type="button" data-href="#hyperloglog-test-2">测试结果</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="hyperloglog-test-1"><p><strong>测试目标</strong></p><p>验证HyperLogLog在百万级数据下的性能表现：</p><ul><li><strong>内存占用</strong>：统计100万条数据的内存消耗</li><li><strong>统计精度</strong>：误差是否在0.81%范围内</li><li><strong>性能表现</strong>：添加和查询操作的耗时</li><li><strong>对比测试</strong>：与Set结构进行内存和性能对比</li></ul><p><strong>测试方案</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testHyperLogLog</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 1.初始化HyperLogLog</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;test:uv:&quot;</span> + System.currentTimeMillis();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2.添加100万条数据</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">total</span> <span class="operator">=</span> <span class="number">1_000_000</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; total; i++) &#123;</span><br><span class="line">        stringRedisTemplate.opsForHyperLogLog().add(key, <span class="string">&quot;user&quot;</span> + i);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3.统计基数</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">count</span> <span class="operator">=</span> stringRedisTemplate.opsForHyperLogLog().size(key);</span><br><span class="line">    System.out.println(<span class="string">&quot;实际数据量：&quot;</span> + total);</span><br><span class="line">    System.out.println(<span class="string">&quot;统计结果：&quot;</span> + count);</span><br><span class="line">    System.out.println(<span class="string">&quot;误差率：&quot;</span> + Math.abs(count - total) * <span class="number">100.0</span> / total + <span class="string">&quot;%&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4.内存占用估算</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">memory</span> <span class="operator">=</span> getMemoryUsage(key);</span><br><span class="line">    System.out.println(<span class="string">&quot;内存占用：&quot;</span> + memory + <span class="string">&quot; bytes&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="hyperloglog-test-2"><p><strong>测试结果</strong></p><p>经过测试发现：</p><ul><li><strong>内存占用</strong>：约12KB，远低于Set结构的几十MB</li><li><strong>统计精度</strong>：误差在0.5%左右，优于官方标称的0.81%</li><li><strong>性能表现</strong>：100万条数据添加耗时约2秒</li><li><strong>空间效率</strong>：相比Set结构，内存节省99%以上</li></ul><p><strong>性能对比</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">| 数据结构 | 内存占用 | 统计精度 | 适用场景 |</span><br><span class="line">|---------|----------|----------|----------|</span><br><span class="line">| Set | 几十MB | 100% | 小数据量精确统计 |</span><br><span class="line">| HyperLogLog | 12KB | 99.2% | 大数据量近似统计 |</span><br></pre></td></tr></table></figure><p><strong>关键点分析</strong></p><ol><li><strong>内存效率</strong>：百万级数据仅需12KB内存，极其高效</li><li><strong>统计精度</strong>：实际误差通常小于理论值，表现优秀</li><li><strong>性能稳定</strong>：数据量增加不会显著影响性能</li><li><strong>生产适用</strong>：完全满足生产环境的UV统计需求</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div>]]></content>
    
    
    <summary type="html">详细介绍Redis的基本概念、安装配置、数据类型、常用命令以及实际应用场景，帮助开发者快速掌握这个强大的内存数据库。</summary>
    
    
    
    <category term="墨香实战" scheme="https://blog.adoreorg.cn/categories/%E5%A2%A8%E9%A6%99%E5%AE%9E%E6%88%98/"/>
    
    
    <category term="缓存" scheme="https://blog.adoreorg.cn/tags/%E7%BC%93%E5%AD%98/"/>
    
    <category term="高性能" scheme="https://blog.adoreorg.cn/tags/%E9%AB%98%E6%80%A7%E8%83%BD/"/>
    
    <category term="Redis" scheme="https://blog.adoreorg.cn/tags/Redis/"/>
    
    <category term="NoSQL" scheme="https://blog.adoreorg.cn/tags/NoSQL/"/>
    
    <category term="分布式" scheme="https://blog.adoreorg.cn/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>Markdown语法与外挂标签写法汇总</title>
    <link href="https://blog.adoreorg.cn/posts/2013454d.html"/>
    <id>https://blog.adoreorg.cn/posts/2013454d.html</id>
    <published>2025-08-09T10:19:03.000Z</published>
    <updated>2025-08-23T14:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1>1.Markdown语法自带格式</h1><div class="note info flat"><p>参考：<a href="https://blog.csdn.net/u014061630/article/details/81359144">Markdown语法图文全面详解(10分钟学会)</a></p></div><div class="note warning flat"><p>注意：此页面偶尔会存在CSS冲突问题!</p></div><h2 id="1-1-代码块">1.1 代码块</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-2">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">\```shell</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">VSCode终端</span></span><br><span class="line">hexo clean; hexo s</span><br><span class="line">hexo clean; hexo g; hexo d</span><br><span class="line">git add .; git commit -m &quot;npm publish&quot;; npm version patch; </span><br><span class="line">git push</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Cmder终端</span></span><br><span class="line">hexo clean &amp;&amp; hexo s</span><br><span class="line">hexo clean &amp;&amp; hexo g &amp;&amp; hexo d</span><br><span class="line">git add . &amp;&amp; git commit -m &quot;npm publish&quot; &amp;&amp; npm version patch</span><br><span class="line">git push</span><br><span class="line">\```</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">VSCode终端</span></span><br><span class="line">hexo clean; hexo s</span><br><span class="line">hexo clean; hexo g; hexo d</span><br><span class="line">git add .; git commit -m &quot;npm publish&quot;; npm version patch; </span><br><span class="line">git push</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Cmder终端</span></span><br><span class="line">hexo clean &amp;&amp; hexo s</span><br><span class="line">hexo clean &amp;&amp; hexo g &amp;&amp; hexo d</span><br><span class="line">git add . &amp;&amp; git commit -m &quot;npm publish&quot; &amp;&amp; npm version patch</span><br><span class="line">git push</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="1-2-多级标题">1.2 多级标题</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-2">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># H1</span></span><br><span class="line"><span class="section">## H2</span></span><br><span class="line"><span class="section">### H3</span></span><br><span class="line"><span class="section">#### H4</span></span><br><span class="line"><span class="section">##### H5</span></span><br><span class="line"><span class="section">###### H6</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><p>见本文章标题!</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="1-3-文字样式">1.3 文字样式</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-2">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">u</span>&gt;</span></span>下划线演示<span class="language-xml"><span class="tag">&lt;/<span class="name">u</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line">文字<span class="strong">**加粗**</span>演示</span><br><span class="line"></span><br><span class="line">文字<span class="emphasis">*斜体*</span>演示</span><br><span class="line"></span><br><span class="line">文本<span class="code">`高亮`</span>演示</span><br><span class="line"></span><br><span class="line">文本~~删除~~线演示</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">font</span> <span class="attr">size</span> = <span class="string">5</span>&gt;</span></span>5号字<span class="language-xml"><span class="tag">&lt;/<span class="name">font</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">font</span> <span class="attr">face</span>=<span class="string">&quot;黑体&quot;</span>&gt;</span></span>黑体<span class="language-xml"><span class="tag">&lt;/<span class="name">font</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">font</span> <span class="attr">color</span>=<span class="string">blue</span>&gt;</span></span>蓝色<span class="language-xml"><span class="tag">&lt;/<span class="name">font</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">table</span>&gt;</span></span><span class="language-xml"><span class="tag">&lt;<span class="name">tr</span>&gt;</span></span><span class="language-xml"><span class="tag">&lt;<span class="name">td</span> <span class="attr">bgcolor</span>=<span class="string">MistyRose</span>&gt;</span></span>这里的背景色是：MistyRosen，此处输入任意想输入的内容<span class="language-xml"><span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><span class="language-xml"><span class="tag">&lt;/<span class="name">tr</span>&gt;</span></span><span class="language-xml"><span class="tag">&lt;/<span class="name">table</span>&gt;</span></span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><p><u>下划线演示</u></p><p>文字<strong>加粗</strong>演示</p><p>文字<em>斜体</em>演示</p><p>文本<code>高亮</code>演示</p><p>文本<s>删除</s>线演示</p><p><font size = 5>5号字</font><br><font face="黑体">黑体</font><br><font color=blue>蓝色</font></p><table><tr><td bgcolor=MistyRose>这里的背景色是：MistyRosen，此处输入任意想输入的内容</td></tr></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><div class="note info flat"><p>上述要点可参考:<a href="https://blog.csdn.net/qq_43732429/article/details/108034518">【Markdown语法】字体颜色大小及文字底色设置</a></p></div><h2 id="1-4-引用">1.4 引用</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-2">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="quote">&gt;  Java</span></span><br><span class="line"><span class="quote">&gt; 二级引用演示</span></span><br><span class="line"><span class="quote">&gt; MySQL</span></span><br><span class="line"><span class="quote">&gt; &gt;外键</span></span><br><span class="line"><span class="quote">&gt; &gt;</span></span><br><span class="line"><span class="quote">&gt; &gt;事务</span></span><br><span class="line"><span class="quote">&gt; &gt;</span></span><br><span class="line"><span class="quote">&gt; &gt;<span class="strong">**行级锁**</span>(引用内部一样可以用格式)</span></span><br><span class="line"><span class="quote">&gt; </span></span><br><span class="line"><span class="quote">&gt; ....</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><blockquote><p>Java<br>二级引用演示<br>MySQL</p><blockquote><p>外键</p><p>事务</p><p><strong>行级锁</strong>(引用内部一样可以用格式)</p></blockquote><p>…</p></blockquote><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="1-5-分割线">1.5 分割线</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-2">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line"><span class="strong">**<span class="emphasis">*</span></span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><hr><hr><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="1-6-列表-跟空格都可以">1.6 列表(*,+,-跟空格都可以)</h2><h3 id="1-6-1-无序列表">1.6.1 无序列表</h3><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-2">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">*</span> Java</span><br><span class="line"><span class="bullet">*</span> Python</span><br><span class="line"><span class="bullet">*</span> ...</span><br><span class="line"></span><br><span class="line"><span class="bullet">+</span> Java</span><br><span class="line"><span class="bullet">+</span> Python</span><br><span class="line"><span class="bullet">+</span> ...</span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> Java</span><br><span class="line"><span class="bullet">-</span> Python</span><br><span class="line"><span class="bullet">-</span> ...</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ul><li>Java</li><li>Python</li><li>…</li></ul><ul><li>Java</li><li>Python</li><li>…</li></ul><ul><li>Java</li><li>Python</li><li>…</li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h3 id="1-6-2-有序列表">1.6.2 有序列表</h3><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-2">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># 注意后面有空格</span></span><br><span class="line"><span class="bullet">1.</span> </span><br><span class="line"><span class="bullet">2.</span> </span><br><span class="line"><span class="bullet">3.</span> </span><br><span class="line"><span class="bullet">4.</span> </span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li></li><li></li><li></li><li></li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="1-7-图片">1.7 图片</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-2">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># 本地图片</span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;/assets/pusheencode.webp&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;示例图片&quot;</span> <span class="attr">style</span>=<span class="string">&quot;zoom:50%;&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="section"># 在线图片</span></span><br><span class="line">![<span class="string">code</span>](<span class="link">https://cdn.jsdelivr.net/gh/fomalhaut1998/markdown_pic/img/code.png</span>)</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><p>本地图片:<br><img src="/assets/pusheencode.webp" alt="示例图片" style="zoom:50%;" /><br>在线图片:<br><img src="https://cdn.jsdelivr.net/gh/fomalhaut1998/markdown_pic/img/code.png" alt="code"></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="1-8-表格">1.8 表格</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-2">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">| 项目标号 | 资金     | 备注 |</span><br><span class="line">| -------- | -------- | ---- |</span><br><span class="line">| 1        | 100，000 | 无   |</span><br><span class="line">| 2        | 200，000 | 无   |</span><br><span class="line">| 3        | 300,600  | 重要 |</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><table><thead><tr><th>项目标号</th><th>资金</th><th>备注</th></tr></thead><tbody><tr><td>1</td><td>100，000</td><td>无</td></tr><tr><td>2</td><td>200，000</td><td>无</td></tr><tr><td>3</td><td>300,600</td><td>重要</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="1-9-公式">1.9 公式</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-2">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$$</span><br><span class="line">\Gamma(z)=\int<span class="emphasis">_0^\infty t^&#123;z-1&#125;e^&#123;-t&#125;dt.</span></span><br><span class="line"><span class="emphasis">$$</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><p>$$<br>\Gamma(z)=\int_0^\infty t^{z-1}e^{-t}dt.<br>$$</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h1>2.Butterfly外挂标签</h1><div class="note info flat"><p>这部分参考安知鱼:<a href="https://anzhiy.cn/posts/7d58.html">基于Butterfly的外挂标签引入</a></p></div><h2 id="2-1-行内文本样式-text">2.1 行内文本样式 text</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-3">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;% u 文本内容 %&#125;</span><br><span class="line">&#123;% emp 文本内容 %&#125;</span><br><span class="line">&#123;% wavy 文本内容 %&#125;</span><br><span class="line">&#123;% del 文本内容 %&#125;</span><br><span class="line">&#123;% kbd 文本内容 %&#125;</span><br><span class="line">&#123;% psw 文本内容 %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">1.</span> 带 &#123;% u 下划线 %&#125; 的文本</span><br><span class="line"><span class="bullet">2.</span> 带 &#123;% emp 着重号 %&#125; 的文本</span><br><span class="line"><span class="bullet">3.</span> 带 &#123;% wavy 波浪线 %&#125; 的文本</span><br><span class="line"><span class="bullet">4.</span> 带 &#123;% del 删除线 %&#125; 的文本</span><br><span class="line"><span class="bullet">5.</span> 键盘样式的文本 &#123;% kbd command %&#125; + &#123;% kbd D %&#125;</span><br><span class="line"><span class="bullet">6.</span> 密码样式的文本：&#123;% psw 这里没有验证码 %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><ol><li>带 <u>下划线</u> 的文本</li><li>带 <emp>着重号</emp> 的文本</li><li>带 <wavy>波浪线</wavy> 的文本</li><li>带 <del>删除线</del> 的文本</li><li>键盘样式的文本 <kbd>command</kbd> + <kbd>D</kbd></li><li>密码样式的文本：<psw>这里没有验证码</psw></li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-2-行内文本-span">2.2 行内文本 span</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">配置参数</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% span 样式参数(参数以空格划分), 文本内容 %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><code>字体</code>: logo, code</li><li><code>颜色</code>: red,yellow,green,cyan,blue,gray</li><li><code>大小</code>: small, h4, h3, h2, h1, large, huge, ultra</li><li><code>对齐方向</code>: left, center, right</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> 彩色文字</span><br><span class="line">在一段话中方便插入各种颜色的标签，包括：&#123;% span red, 红色 %&#125;、&#123;% span yellow, 黄色 %&#125;、&#123;% span green, 绿色 %&#125;、&#123;% span cyan, 青色 %&#125;、&#123;% span blue, 蓝色 %&#125;、&#123;% span gray, 灰色 %&#125;。</span><br><span class="line"><span class="bullet">-</span> 超大号文字</span><br><span class="line">文档「开始」页面中的标题部分就是超大号文字。</span><br><span class="line">&#123;% span center logo large, Volantis %&#125;</span><br><span class="line">&#123;% span center small, A Wonderful Theme for Hexo %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><ul><li>彩色文字<br>在一段话中方便插入各种颜色的标签，包括：<span class='p red'>红色</span>、<span class='p yellow'>黄色</span>、<span class='p green'>绿色</span>、<span class='p cyan'>青色</span>、<span class='p blue'>蓝色</span>、<span class='p gray'>灰色</span>。</li><li>超大号文字<br>文档「开始」页面中的标题部分就是超大号文字。<br><span class='p center logo large'>Volantis</span><br><span class='p center small'>A Wonderful Theme for Hexo</span></li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-3-段落文本-p">2.3 段落文本 p</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">配置参数</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% p 样式参数(参数以空格划分), 文本内容 %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><code>字体</code>: logo, code</li><li><code>颜色</code>: red,yellow,green,cyan,blue,gray</li><li><code>大小</code>: small, h4, h3, h2, h1, large, huge, ultra</li><li><code>对齐方向</code>: left, center, right</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> 彩色文字</span><br><span class="line">在一段话中方便插入各种颜色的标签，包括：&#123;% p red, 红色 %&#125;、&#123;% p yellow, 黄色 %&#125;、&#123;% p green, 绿色 %&#125;、&#123;% p cyan, 青色 %&#125;、&#123;% p blue, 蓝色 %&#125;、&#123;% p gray, 灰色 %&#125;。</span><br><span class="line"><span class="bullet">-</span> 超大号文字</span><br><span class="line">文档「开始」页面中的标题部分就是超大号文字。</span><br><span class="line">&#123;% p center logo large, Volantis %&#125;</span><br><span class="line">&#123;% p center small, A Wonderful Theme for Hexo %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><ul><li>彩色文字<br>在一段话中方便插入各种颜色的标签，包括：<p class='p red'>红色</p>、<p class='p yellow'>黄色</p>、<p class='p green'>绿色</p>、<p class='p cyan'>青色</p>、<p class='p blue'>蓝色</p>、<p class='p gray'>灰色</p>。</li><li>超大号文字<br>文档「开始」页面中的标题部分就是超大号文字。</li></ul><p class='p center logo large'>Volantis</p><p class='p center small'>A Wonderful Theme for Hexo</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-4-引用note">2.4 引用note</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">通用配置</button></li><li class="tab"><button type="button" data-href="#分栏-2">语法格式</button></li><li class="tab"><button type="button" data-href="#分栏-3">参数配置</button></li><li class="tab"><button type="button" data-href="#分栏-4">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-5">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">note:</span><br><span class="line">  # Note tag style values:</span><br><span class="line">  #  - simple    bs-callout old alert style. Default.</span><br><span class="line">  #  - modern    bs-callout new (v2-v3) alert style.</span><br><span class="line">  #  - flat      flat callout style with background, like on Mozilla or StackOverflow.</span><br><span class="line">  #  - disabled  disable all CSS styles import of note tag.</span><br><span class="line">  style: simple</span><br><span class="line">  icons: false</span><br><span class="line">  border<span class="emphasis">_radius: 3</span></span><br><span class="line"><span class="emphasis">  # Offset lighter of background in % for modern and flat styles (modern: -12 | 12; flat: -18 | 6).</span></span><br><span class="line"><span class="emphasis">  # Offset also applied to label tag variables. This option can work with disabled note tag.</span></span><br><span class="line"><span class="emphasis">  light_</span>bg<span class="emphasis">_offset: 0</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># 自带icon</span></span><br><span class="line">&#123;% note [class] [no-icon] [style] %&#125;</span><br><span class="line">Any content (support inline tags too.io).</span><br><span class="line">&#123;% endnote %&#125;</span><br><span class="line"><span class="section"># 外部icon</span></span><br><span class="line">&#123;% note [color] [icon] [style] %&#125;</span><br><span class="line">Any content (support inline tags too.io).</span><br><span class="line">&#123;% endnote %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><p>1.自带icon</p><table><thead><tr><th>参数</th><th style="text-align:center">用法</th></tr></thead><tbody><tr><td>class</td><td style="text-align:center">【可选】标识，不同的标识有不同的配色 （ default / primary / success / info / warning / danger ）</td></tr><tr><td>no-icon</td><td style="text-align:center">【可选】不显示 icon</td></tr><tr><td>style</td><td style="text-align:center">【可选】可以覆盖配置中的 style （simple/modern/flat/disabled）</td></tr></tbody></table><p>2.外部icon</p><table><thead><tr><th>参数</th><th style="text-align:center">用法</th></tr></thead><tbody><tr><td>class</td><td style="text-align:center">【可选】标识，不同的标识有不同的配色 （ default / blue / pink / red / purple / orange / green ）</td></tr><tr><td>no-icon</td><td style="text-align:center">【可选】可配置自定义 icon (只支持 fontawesome 图标, 也可以配置 no-icon )</td></tr><tr><td>style</td><td style="text-align:center">【可选】可以覆盖配置中的 style （simple/modern/flat/disabled）</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><details class="folding-tag" blue><summary> 1.自带icon </summary>              <div class='content'>              <p>1.<code>simple</code>样式</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;% note simple %&#125;默认 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note default simple %&#125;default 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note primary simple %&#125;primary 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note success simple %&#125;success 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note info simple %&#125;info 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note warning simple %&#125;warning 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note danger simple %&#125;danger 提示块标签&#123;% endnote %&#125;</span><br></pre></td></tr></table></figure><p>2.<code>modern</code>样式</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;% note modern %&#125;默认 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note default modern %&#125;default 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note primary modern %&#125;primary 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note success modern %&#125;success 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note info modern %&#125;info 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note warning modern %&#125;warning 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note danger modern %&#125;danger 提示块标签&#123;% endnote %&#125;</span><br></pre></td></tr></table></figure><p>3.<code>flat</code>样式</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;% note flat %&#125;默认 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note default flat %&#125;default 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note primary flat %&#125;primary 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note success flat %&#125;success 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note info flat %&#125;info 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note warning flat %&#125;warning 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note danger flat %&#125;danger 提示块标签&#123;% endnote %&#125;</span><br></pre></td></tr></table></figure><p>4.<code>disabled</code>样式</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;% note disabled %&#125;默认 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note default disabled %&#125;default 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note primary disabled %&#125;primary 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note success disabled %&#125;success 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note info disabled %&#125;info 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note warning disabled %&#125;warning 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note danger disabled %&#125;danger 提示块标签&#123;% endnote %&#125;</span><br></pre></td></tr></table></figure><p>5.<code>no-icon</code>样式</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;% note no-icon %&#125;默认 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note default no-icon %&#125;default 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note primary no-icon %&#125;primary 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note success no-icon %&#125;success 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note info no-icon %&#125;info 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note warning no-icon %&#125;warning 提示块标签&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note danger no-icon %&#125;danger 提示块标签&#123;% endnote %&#125;</span><br></pre></td></tr></table></figure>              </div>            </details><details class="folding-tag" blue><summary> 2.外部icon </summary>              <div class='content'>              <p>1.<code>simple</code>样式</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;% note &#x27;fab fa-cc-visa&#x27; simple %&#125;你是刷 Visa 还是 UnionPay&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note blue &#x27;fas fa-bullhorn&#x27; simple %&#125;2021年快到了....&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note pink &#x27;fas fa-car-crash&#x27; simple %&#125;小心开车 安全至上&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note red &#x27;fas fa-fan&#x27; simple%&#125;这是三片呢？还是四片？&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note orange &#x27;fas fa-battery-half&#x27; simple %&#125;你是刷 Visa 还是 UnionPay&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note purple &#x27;far fa-hand-scissors&#x27; simple %&#125;剪刀石头布&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note green &#x27;fab fa-internet-explorer&#x27; simple %&#125;前端最讨厌的浏览器&#123;% endnote %&#125;</span><br></pre></td></tr></table></figure><p>2.<code>modern</code>样式</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;% note &#x27;fab fa-cc-visa&#x27; modern %&#125;你是刷 Visa 还是 UnionPay&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note blue &#x27;fas fa-bullhorn&#x27; modern %&#125;2021年快到了....&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note pink &#x27;fas fa-car-crash&#x27; modern %&#125;小心开车 安全至上&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note red &#x27;fas fa-fan&#x27; modern%&#125;这是三片呢？还是四片？&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note orange &#x27;fas fa-battery-half&#x27; modern %&#125;你是刷 Visa 还是 UnionPay&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note purple &#x27;far fa-hand-scissors&#x27; modern %&#125;剪刀石头布&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note green &#x27;fab fa-internet-explorer&#x27; modern %&#125;前端最讨厌的浏览器&#123;% endnote %&#125;</span><br></pre></td></tr></table></figure><p>3.<code>flat</code>样式</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;% note &#x27;fab fa-cc-visa&#x27; flat %&#125;你是刷 Visa 还是 UnionPay&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note blue &#x27;fas fa-bullhorn&#x27; flat %&#125;2021年快到了....&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note pink &#x27;fas fa-car-crash&#x27; flat %&#125;小心开车 安全至上&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note red &#x27;fas fa-fan&#x27; flat%&#125;这是三片呢？还是四片？&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note orange &#x27;fas fa-battery-half&#x27; flat %&#125;你是刷 Visa 还是 UnionPay&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note purple &#x27;far fa-hand-scissors&#x27; flat %&#125;剪刀石头布&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note green &#x27;fab fa-internet-explorer&#x27; flat %&#125;前端最讨厌的浏览器&#123;% endnote %&#125;</span><br></pre></td></tr></table></figure><p>4.<code>disabled</code>样式</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;% note &#x27;fab fa-cc-visa&#x27; disabled %&#125;你是刷 Visa 还是 UnionPay&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note blue &#x27;fas fa-bullhorn&#x27; disabled %&#125;2021年快到了....&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note pink &#x27;fas fa-car-crash&#x27; disabled %&#125;小心开车 安全至上&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note red &#x27;fas fa-fan&#x27; disabled %&#125;这是三片呢？还是四片？&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note orange &#x27;fas fa-battery-half&#x27; disabled %&#125;你是刷 Visa 还是 UnionPay&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note purple &#x27;far fa-hand-scissors&#x27; disabled %&#125;剪刀石头布&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note green &#x27;fab fa-internet-explorer&#x27; disabled %&#125;前端最讨厌的浏览器&#123;% endnote %&#125;</span><br></pre></td></tr></table></figure><p>5.<code>no-icon</code>样式</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;% note no-icon %&#125;你是刷 Visa 还是 UnionPay&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note blue no-icon %&#125;2021年快到了....&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note pink no-icon %&#125;小心开车 安全至上&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note red no-icon %&#125;这是三片呢？还是四片？&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note orange no-icon %&#125;你是刷 Visa 还是 UnionPay&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note purple no-icon %&#125;剪刀石头布&#123;% endnote %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% note green no-icon %&#125;前端最讨厌的浏览器&#123;% endnote %&#125;</span><br></pre></td></tr></table></figure>              </div>            </details><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-5"><details class="folding-tag" blue><summary> 1.自带icon </summary>              <div class='content'>              <p>1.<code>simple</code>样式</p><div class="note simple"><p>默认 提示块标签</p></div><div class="note default simple"><p>default 提示块标签</p></div><div class="note primary simple"><p>primary 提示块标签</p></div><div class="note success simple"><p>success 提示块标签</p></div><div class="note info simple"><p>info 提示块标签</p></div><div class="note warning simple"><p>warning 提示块标签</p></div><div class="note danger simple"><p>danger 提示块标签</p></div>2.`modern`样式<div class="note modern"><p>默认 提示块标签</p></div><div class="note default modern"><p>default 提示块标签</p></div><div class="note primary modern"><p>primary 提示块标签</p></div><div class="note success modern"><p>success 提示块标签</p></div><div class="note info modern"><p>info 提示块标签</p></div><div class="note warning modern"><p>warning 提示块标签</p></div><div class="note danger modern"><p>danger 提示块标签</p></div><p>3.<code>flat</code>样式</p><div class="note flat"><p>默认 提示块标签</p></div><div class="note default flat"><p>default 提示块标签</p></div><div class="note primary flat"><p>primary 提示块标签</p></div><div class="note success flat"><p>success 提示块标签</p></div><div class="note info flat"><p>info 提示块标签</p></div><div class="note warning flat"><p>warning 提示块标签</p></div><div class="note danger flat"><p>danger 提示块标签</p></div><p>4.<code>disabled</code>样式</p><div class="note disabled"><p>默认 提示块标签</p></div><div class="note default disabled"><p>default 提示块标签</p></div><div class="note primary disabled"><p>primary 提示块标签</p></div><div class="note success disabled"><p>success 提示块标签</p></div><div class="note info disabled"><p>info 提示块标签</p></div><div class="note warning disabled"><p>warning 提示块标签</p></div><div class="note danger disabled"><p>danger 提示块标签</p></div><p>5.<code>no-icon</code>样式</p><div class="note no-icon flat"><p>默认 提示块标签</p></div><div class="note default no-icon flat"><p>default 提示块标签</p></div><div class="note primary no-icon flat"><p>primary 提示块标签</p></div><div class="note success no-icon flat"><p>success 提示块标签</p></div><div class="note info no-icon flat"><p>info 提示块标签</p></div><div class="note warning no-icon flat"><p>warning 提示块标签</p></div><div class="note danger no-icon flat"><p>danger 提示块标签</p></div>              </div>            </details><details class="folding-tag" blue><summary> 2.外部icon </summary>              <div class='content'>              <p>1.<code>simple</code>样式</p><div class="note icon-padding simple"><i class="note-icon fab fa-cc-visa"></i><p>你是刷 Visa 还是 UnionPay</p></div><div class="note blue icon-padding simple"><i class="note-icon fas fa-bullhorn"></i><p>2021年快到了…</p></div><div class="note pink icon-padding simple"><i class="note-icon fas fa-car-crash"></i><p>小心开车 安全至上</p></div><div class="note red icon-padding simple"><i class="note-icon fas fa-fan"></i><p>这是三片呢？还是四片？</p></div><div class="note orange icon-padding simple"><i class="note-icon fas fa-battery-half"></i><p>你是刷 Visa 还是 UnionPay</p></div><div class="note purple icon-padding simple"><i class="note-icon far fa-hand-scissors"></i><p>剪刀石头布</p></div><div class="note green icon-padding simple"><i class="note-icon fab fa-internet-explorer"></i><p>前端最讨厌的浏览器</p></div><p>2.<code>modern</code>样式</p><div class="note icon-padding modern"><i class="note-icon fab fa-cc-visa"></i><p>你是刷 Visa 还是 UnionPay</p></div><div class="note blue icon-padding modern"><i class="note-icon fas fa-bullhorn"></i><p>2021年快到了…</p></div><div class="note pink icon-padding modern"><i class="note-icon fas fa-car-crash"></i><p>小心开车 安全至上</p></div><div class="note red icon-padding modern"><i class="note-icon fas fa-fan"></i><p>这是三片呢？还是四片？</p></div><div class="note orange icon-padding modern"><i class="note-icon fas fa-battery-half"></i><p>你是刷 Visa 还是 UnionPay</p></div><div class="note purple icon-padding modern"><i class="note-icon far fa-hand-scissors"></i><p>剪刀石头布</p></div><div class="note green icon-padding modern"><i class="note-icon fab fa-internet-explorer"></i><p>前端最讨厌的浏览器</p></div><p>3.<code>flat</code>样式</p><div class="note icon-padding flat"><i class="note-icon fab fa-cc-visa"></i><p>你是刷 Visa 还是 UnionPay</p></div><div class="note blue icon-padding flat"><i class="note-icon fas fa-bullhorn"></i><p>2021年快到了…</p></div><div class="note pink icon-padding flat"><i class="note-icon fas fa-car-crash"></i><p>小心开车 安全至上</p></div><div class="note red icon-padding flat"><i class="note-icon fas fa-fan"></i><p>这是三片呢？还是四片？</p></div><div class="note orange icon-padding flat"><i class="note-icon fas fa-battery-half"></i><p>你是刷 Visa 还是 UnionPay</p></div><div class="note purple icon-padding flat"><i class="note-icon far fa-hand-scissors"></i><p>剪刀石头布</p></div><div class="note green icon-padding flat"><i class="note-icon fab fa-internet-explorer"></i><p>前端最讨厌的浏览器</p></div><p>4.<code>disabled</code>样式</p><div class="note icon-padding disabled"><i class="note-icon fab fa-cc-visa"></i><p>你是刷 Visa 还是 UnionPay</p></div><div class="note blue icon-padding disabled"><i class="note-icon fas fa-bullhorn"></i><p>2021年快到了…</p></div><div class="note pink icon-padding disabled"><i class="note-icon fas fa-car-crash"></i><p>小心开车 安全至上</p></div><div class="note red icon-padding disabled"><i class="note-icon fas fa-fan"></i><p>这是三片呢？还是四片？</p></div><div class="note orange icon-padding disabled"><i class="note-icon fas fa-battery-half"></i><p>你是刷 Visa 还是 UnionPay</p></div><div class="note purple icon-padding disabled"><i class="note-icon far fa-hand-scissors"></i><p>剪刀石头布</p></div><div class="note green icon-padding disabled"><i class="note-icon fab fa-internet-explorer"></i><p>前端最讨厌的浏览器</p></div><p>5.<code>no-icon</code>样式</p><div class="note no-icon flat"><p>你是刷 Visa 还是 UnionPay</p></div><div class="note blue no-icon flat"><p>2021年快到了…</p></div><div class="note pink no-icon flat"><p>小心开车 安全至上</p></div><div class="note red no-icon flat"><p>这是三片呢？还是四片？</p></div><div class="note orange no-icon flat"><p>你是刷 Visa 还是 UnionPay</p></div><div class="note purple no-icon flat"><p>剪刀石头布</p></div><div class="note green no-icon flat"><p>前端最讨厌的浏览器</p></div>              </div>            </details><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-5-上标标签-tip">2.5 上标标签 tip</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">配置参数</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% tip [参数，可选] %&#125;文本内容&#123;% endtip %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><code>样式</code>: success,error,warning,bolt,ban,home,sync,cogs,key,bell</li><li><code>自定义图标</code>: 支持fontawesome。</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;% tip %&#125;default&#123;% endtip %&#125;</span><br><span class="line">&#123;% tip info %&#125;info&#123;% endtip %&#125;</span><br><span class="line">&#123;% tip success %&#125;success&#123;% endtip %&#125;</span><br><span class="line">&#123;% tip error %&#125;error&#123;% endtip %&#125;</span><br><span class="line">&#123;% tip warning %&#125;warning&#123;% endtip %&#125;</span><br><span class="line">&#123;% tip bolt %&#125;bolt&#123;% endtip %&#125;</span><br><span class="line">&#123;% tip ban %&#125;ban&#123;% endtip %&#125;</span><br><span class="line">&#123;% tip home %&#125;home&#123;% endtip %&#125;</span><br><span class="line">&#123;% tip sync %&#125;sync&#123;% endtip %&#125;</span><br><span class="line">&#123;% tip cogs %&#125;cogs&#123;% endtip %&#125;</span><br><span class="line">&#123;% tip key %&#125;key&#123;% endtip %&#125;</span><br><span class="line">&#123;% tip bell %&#125;bell&#123;% endtip %&#125;</span><br><span class="line">&#123;% tip fa-atom %&#125;自定义font awesome图标&#123;% endtip %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><div class="tip "><p>default</p></div><div class="tip info"><p>info</p></div><div class="tip success"><p>success</p></div><div class="tip error"><p>error</p></div><div class="tip warning"><p>warning</p></div><div class="tip bolt"><p>bolt</p></div><div class="tip ban"><p>ban</p></div><div class="tip home"><p>home</p></div><div class="tip sync"><p>sync</p></div><div class="tip cogs"><p>cogs</p></div><div class="tip key"><p>key</p></div><div class="tip bell"><p>bell</p></div><div class="tip fa-atom"><p>自定义font awesome图标</p></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-6-动态标签-anima">2.6 动态标签 anima</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-2">配置参数</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% tip [参数，可选] %&#125;文本内容&#123;% endtip %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><div class="note info flat"><ol><li>将所需的CSS类添加到图标（或DOM中的任何元素）。</li><li>对于父级悬停样式，需要给目标元素添加指定CSS类，同时还要给目标元素的父级元素添加CSS类<code>faa-parent animated-hover</code>。（详情见示例及示例源码）<br>You can regulate the speed of the animation by adding the CSS class or . faa-fastfaa-slow</li><li>可以通过给目标元素添加CSS类<code>faa-fast</code>或<code>faa-slow</code>来控制动画快慢。</li></ol></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><p>1.On DOM load（当页面加载时显示动画）</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#123;% tip warning faa-horizontal animated %&#125;warning&#123;% endtip %&#125;</span><br><span class="line">&#123;% tip ban faa-flash animated %&#125;ban&#123;% endtip %&#125;</span><br></pre></td></tr></table></figure><p>2.调整动画速度</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#123;% tip warning faa-horizontal animated faa-fast %&#125;warning&#123;% endtip %&#125;</span><br><span class="line">&#123;% tip ban faa-flash animated faa-slow %&#125;ban&#123;% endtip %&#125;</span><br></pre></td></tr></table></figure><p>3.On hover（当鼠标悬停时显示动画）</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#123;% tip warning faa-horizontal animated-hover %&#125;warning&#123;% endtip %&#125;</span><br><span class="line">&#123;% tip ban faa-flash animated-hover %&#125;ban&#123;% endtip %&#125;</span><br></pre></td></tr></table></figure><p>4.On parent hover（当鼠标悬停在父级元素时显示动画）</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#123;% tip warning faa-parent animated-hover %&#125;<span class="language-xml"><span class="tag">&lt;<span class="name">p</span> <span class="attr">class</span>=<span class="string">&quot;faa-horizontal&quot;</span>&gt;</span></span>warning<span class="language-xml"><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span>&#123;% endtip %&#125;</span><br><span class="line">&#123;% tip ban faa-parent animated-hover %&#125;<span class="language-xml"><span class="tag">&lt;<span class="name">p</span> <span class="attr">class</span>=<span class="string">&quot;faa-flash&quot;</span>&gt;</span></span>ban<span class="language-xml"><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span>&#123;% endtip %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><p>1.On DOM load（当页面加载时显示动画）</p><div class="tip warning faa-horizontal animated"><p>warning</p></div><div class="tip ban faa-flash animated"><p>ban</p></div>2.调整动画速度<div class="tip warning faa-horizontal animated faa-fast"><p>warning</p></div><div class="tip ban faa-flash animated faa-slow"><p>ban</p></div>3.On hover（当鼠标悬停时显示动画）<div class="tip warning faa-horizontal animated-hover"><p>warning</p></div><div class="tip ban faa-flash animated-hover"><p>ban</p></div>4.On parent hover（当鼠标悬停在父级元素时显示动画）<div class="tip warning faa-parent animated-hover"><p class="faa-horizontal">warning</p></div><div class="tip ban faa-parent animated-hover"><p class="faa-flash">ban</p></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-7-复选列表-checkbox">2.7 复选列表 checkbox</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">配置参数</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% checkbox 样式参数（可选）, 文本（支持简单md） %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><code>样式</code>: plus, minus, times</li><li><code>颜色</code>: red,yellow,green,cyan,blue,gray</li><li><code>选中状态</code>: checked</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&#123;% checkbox 纯文本测试 %&#125;</span><br><span class="line">&#123;% checkbox checked, 支持简单的 [<span class="string">markdown</span>](<span class="link">https://guides.github.com/features/mastering-markdown/</span>) 语法 %&#125;</span><br><span class="line">&#123;% checkbox red, 支持自定义颜色 %&#125;</span><br><span class="line">&#123;% checkbox green checked, 绿色 + 默认选中 %&#125;</span><br><span class="line">&#123;% checkbox yellow checked, 黄色 + 默认选中 %&#125;</span><br><span class="line">&#123;% checkbox cyan checked, 青色 + 默认选中 %&#125;</span><br><span class="line">&#123;% checkbox blue checked, 蓝色 + 默认选中 %&#125;</span><br><span class="line">&#123;% checkbox plus green checked, 增加 %&#125;</span><br><span class="line">&#123;% checkbox minus yellow checked, 减少 %&#125;</span><br><span class="line">&#123;% checkbox times red checked, 叉 %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><div class='checkbox'><input type="checkbox" />            <p>纯文本测试</p>            </div><div class='checkbox checked'><input type="checkbox" checked="checked"/>            <p>支持简单的 <a href="https://guides.github.com/features/mastering-markdown/">markdown</a> 语法</p>            </div><div class='checkbox red'><input type="checkbox" />            <p>支持自定义颜色</p>            </div><div class='checkbox green checked'><input type="checkbox" checked="checked"/>            <p>绿色 + 默认选中</p>            </div><div class='checkbox yellow checked'><input type="checkbox" checked="checked"/>            <p>黄色 + 默认选中</p>            </div><div class='checkbox cyan checked'><input type="checkbox" checked="checked"/>            <p>青色 + 默认选中</p>            </div><div class='checkbox blue checked'><input type="checkbox" checked="checked"/>            <p>蓝色 + 默认选中</p>            </div><div class='checkbox plus green checked'><input type="checkbox" checked="checked"/>            <p>增加</p>            </div><div class='checkbox minus yellow checked'><input type="checkbox" checked="checked"/>            <p>减少</p>            </div><div class='checkbox times red checked'><input type="checkbox" checked="checked"/>            <p>叉</p>            </div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-8-单选列表-radio">2.8 单选列表 radio</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">配置参数</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% radio 样式参数（可选）, 文本（支持简单md） %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><code>颜色</code>: red,yellow,green,cyan,blue,gray</li><li><code>选中状态</code>: checked</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#123;% radio 纯文本测试 %&#125;</span><br><span class="line">&#123;% radio checked, 支持简单的 [<span class="string">markdown</span>](<span class="link">https://guides.github.com/features/mastering-markdown/</span>) 语法 %&#125;</span><br><span class="line">&#123;% radio red, 支持自定义颜色 %&#125;</span><br><span class="line">&#123;% radio green, 绿色 %&#125;</span><br><span class="line">&#123;% radio yellow, 黄色 %&#125;</span><br><span class="line">&#123;% radio cyan, 青色 %&#125;</span><br><span class="line">&#123;% radio blue, 蓝色 %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><div class='checkbox'><input type="radio" />            <p>纯文本测试</p>            </div><div class='checkbox checked'><input type="radio" checked="checked"/>            <p>支持简单的 <a href="https://guides.github.com/features/mastering-markdown/">markdown</a> 语法</p>            </div><div class='checkbox red'><input type="radio" />            <p>支持自定义颜色</p>            </div><div class='checkbox green'><input type="radio" />            <p>绿色</p>            </div><div class='checkbox yellow'><input type="radio" />            <p>黄色</p>            </div><div class='checkbox cyan'><input type="radio" />            <p>青色</p>            </div><div class='checkbox blue'><input type="radio" />            <p>蓝色</p>            </div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-9-时间轴-timeline">2.9 时间轴 timeline</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">配置参数</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;% timeline 时间线标题（可选）[,color] %&#125;</span><br><span class="line">&lt;!-- timeline 时间节点（标题） --&gt;</span><br><span class="line">正文内容</span><br><span class="line">&lt;!-- endtimeline --&gt;</span><br><span class="line">&lt;!-- timeline 时间节点（标题） --&gt;</span><br><span class="line">正文内容</span><br><span class="line">&lt;!-- endtimeline --&gt;</span><br><span class="line">&#123;% endtimeline %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><code>title</code>:标题/时间线</li><li><code>color</code>:<code>timeline</code>颜色:default(留空) / blue / pink / red / purple / orange / green</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">&#123;% timeline 时间轴样式,blue %&#125;</span><br><span class="line"></span><br><span class="line">&lt;!-- timeline 2020-07-24 [<span class="string">2.6.6 -&gt; 3.0</span>](<span class="link">https://github.com/volantis-x/hexo-theme-volantis/releases</span>) --&gt;</span><br><span class="line"></span><br><span class="line"><span class="bullet">1.</span> 如果有 <span class="code">`hexo-lazyload-image`</span> 插件，需要删除并重新安装最新版本，设置 <span class="code">`lazyload.isSPA: true`</span>。</span><br><span class="line"><span class="bullet">2.</span> 2.x 版本的 css 和 js 不适用于 3.x 版本，如果使用了 <span class="code">`use_cdn: true`</span> 则需要删除。</span><br><span class="line"><span class="bullet">3.</span> 2.x 版本的 fancybox 标签在 3.x 版本中被重命名为 gallery 。</span><br><span class="line"><span class="bullet">4.</span> 2.x 版本的置顶 <span class="code">`top: true`</span> 改为了 <span class="code">`pin: true`</span>，并且同样适用于 <span class="code">`layout: page`</span> 的页面。</span><br><span class="line"><span class="bullet">5.</span> 如果使用了 <span class="code">`hexo-offline`</span> 插件，建议卸载，3.0 版本默认开启了 pjax 服务。</span><br><span class="line"></span><br><span class="line">&lt;!-- endtimeline --&gt;</span><br><span class="line"></span><br><span class="line">&lt;!-- timeline 2020-05-15 [<span class="string">2.6.3 -&gt; 2.6.6</span>](<span class="link">https://github.com/volantis-x/hexo-theme-volantis/releases/tag/2.6.6</span>) --&gt;</span><br><span class="line"></span><br><span class="line">不需要额外处理。</span><br><span class="line"></span><br><span class="line">&lt;!-- endtimeline --&gt;</span><br><span class="line"></span><br><span class="line">&lt;!-- timeline 2020-04-20 [<span class="string">2.6.2 -&gt; 2.6.3</span>](<span class="link">https://github.com/volantis-x/hexo-theme-volantis/releases/tag/2.6.3</span>) --&gt;</span><br><span class="line"></span><br><span class="line"><span class="bullet">1.</span> 全局搜索 <span class="code">`seotitle`</span> 并替换为 <span class="code">`seo_title`</span>。</span><br><span class="line"><span class="bullet">2.</span> group 组件的索引规则有变，使用 group 组件的文章内，<span class="code">`group: group_name`</span> 对应的组件名必须是 <span class="code">`group_name`</span>。</span><br><span class="line"><span class="bullet">2.</span> group 组件的列表名优先显示文章的 <span class="code">`short_title`</span> 其次是 <span class="code">`title`</span>。</span><br><span class="line"></span><br><span class="line">&lt;!-- endtimeline --&gt;</span><br><span class="line"></span><br><span class="line">&#123;% endtimeline %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><div class="timeline blue"><div class='timeline-item headline'><div class='timeline-item-title'><div class='item-circle'><p>时间轴样式</p></div></div></div><div class='timeline-item'><div class='timeline-item-title'><div class='item-circle'><p>2020-07-24 <a href="https://github.com/volantis-x/hexo-theme-volantis/releases">2.6.6 -&gt; 3.0</a></p></div></div><div class='timeline-item-content'><ol><li>如果有 <code>hexo-lazyload-image</code> 插件，需要删除并重新安装最新版本，设置 <code>lazyload.isSPA: true</code>。</li><li>2.x 版本的 css 和 js 不适用于 3.x 版本，如果使用了 <code>use_cdn: true</code> 则需要删除。</li><li>2.x 版本的 fancybox 标签在 3.x 版本中被重命名为 gallery 。</li><li>2.x 版本的置顶 <code>top: true</code> 改为了 <code>pin: true</code>，并且同样适用于 <code>layout: page</code> 的页面。</li><li>如果使用了 <code>hexo-offline</code> 插件，建议卸载，3.0 版本默认开启了 pjax 服务。</li></ol></div></div><div class='timeline-item'><div class='timeline-item-title'><div class='item-circle'><p>2020-05-15 <a href="https://github.com/volantis-x/hexo-theme-volantis/releases/tag/2.6.6">2.6.3 -&gt; 2.6.6</a></p></div></div><div class='timeline-item-content'><p>不需要额外处理。</p></div></div><div class='timeline-item'><div class='timeline-item-title'><div class='item-circle'><p>2020-04-20 <a href="https://github.com/volantis-x/hexo-theme-volantis/releases/tag/2.6.3">2.6.2 -&gt; 2.6.3</a></p></div></div><div class='timeline-item-content'><ol><li>全局搜索 <code>seotitle</code> 并替换为 <code>seo_title</code>。</li><li>group 组件的索引规则有变，使用 group 组件的文章内，<code>group: group_name</code> 对应的组件名必须是 <code>group_name</code>。</li><li>group 组件的列表名优先显示文章的 <code>short_title</code> 其次是 <code>title</code>。</li></ol></div></div></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-10-链接卡片-link">2.10 链接卡片 link</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-3">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% link 标题, 链接, 图片链接（可选） %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% link 糖果屋教程贴, https://akilar.top/posts/615e2dec/, https://cdn.cbd.int/akilar-candyassets@1.0.36/image/siteicon/favicon.ico %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><div class='liushen-tag-link'><a class="tag-Link" target="_blank" href=" https://cdn.cbd.int/akilar-candyassets@1.0.36/image/siteicon/favicon.ico">    <div class="tag-link-tips">🪧引用站外地址，不保证站点的可用性和安全性</div>    <div class="tag-link-bottom">        <div class="tag-link-left" style="background-image: url(https://source.adoreorg.cn/webp/icon/66a4632bbf06e.webp);"></div>        <div class="tag-link-right">            <div class="tag-link-title">糖果屋教程贴</div>            <div class="tag-link-sitename"> https://akilar.top/posts/615e2dec/</div>        </div>        <i class="fa-solid fa-angle-right"></i>    </div>    </a></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-11-按钮-btns">2.11 按钮 btns</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">参数配置</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;% btns 样式参数 %&#125;</span><br><span class="line">&#123;% cell 标题, 链接, 图片或者图标 %&#125;</span><br><span class="line">&#123;% cell 标题, 链接, 图片或者图标 %&#125;</span><br><span class="line">&#123;% endbtns %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li>圆角样式：rounded, circle</li><li>增加文字样式：可以在容器内增加 <code>&lt;b&gt;</code>标题<code>&lt;/b&gt;</code>和<code>&lt;p&gt;</code>描述文字<code>&lt;/p&gt;</code></li><li>布局方式：<br>默认为自动宽度，适合视野内只有一两个的情况。</li></ol><table><thead><tr><th>参数</th><th>含义</th></tr></thead><tbody><tr><td>wide</td><td>宽一点的按钮</td></tr><tr><td>fill</td><td>填充布局，自动铺满至少一行，多了会换行</td></tr><tr><td>center</td><td>居中，按钮之间是固定间距</td></tr><tr><td>around</td><td>居中分散</td></tr><tr><td>grid2</td><td>等宽最多2列，屏幕变窄会适当减少列数</td></tr><tr><td>grid3</td><td>等宽最多3列，屏幕变窄会适当减少列数</td></tr><tr><td>grid4</td><td>等宽最多4列，屏幕变窄会适当减少列数</td></tr><tr><td>grid5</td><td>等宽最多5列，屏幕变窄会适当减少列数</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><p>1.如果需要显示类似「团队成员」之类的一组含有头像的链接</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#123;% btns circle grid5 %&#125;</span><br><span class="line">&#123;% cell xaoxuu, https://xaoxuu.com, https://cdn.jsdelivr.net/gh/xaoxuu/cdn-assets/avatar/avatar.png %&#125;</span><br><span class="line">&#123;% cell xaoxuu, https://xaoxuu.com, https://cdn.jsdelivr.net/gh/xaoxuu/cdn-assets/avatar/avatar.png %&#125;</span><br><span class="line">&#123;% cell xaoxuu, https://xaoxuu.com, https://cdn.jsdelivr.net/gh/xaoxuu/cdn-assets/avatar/avatar.png %&#125;</span><br><span class="line">&#123;% cell xaoxuu, https://xaoxuu.com, https://cdn.jsdelivr.net/gh/xaoxuu/cdn-assets/avatar/avatar.png %&#125;</span><br><span class="line">&#123;% cell xaoxuu, https://xaoxuu.com, https://cdn.jsdelivr.net/gh/xaoxuu/cdn-assets/avatar/avatar.png %&#125;</span><br><span class="line">&#123;% endbtns %&#125;</span><br></pre></td></tr></table></figure><p>2.或者含有图标的按钮</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;% btns rounded grid5 %&#125;</span><br><span class="line">&#123;% cell 下载源码, /, fas fa-download %&#125;</span><br><span class="line">&#123;% cell 查看文档, /, fas fa-book-open %&#125;</span><br><span class="line">&#123;% endbtns %&#125;</span><br></pre></td></tr></table></figure><p>3.圆形图标 + 标题 + 描述 + 图片 + 网格5列 + 居中</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">&#123;% btns circle center grid5 %&#125;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&#x27;https://apps.apple.com/cn/app/heart-mate-pro-hrm-utility/id1463348922?ls=1&#x27;</span>&gt;</span></span></span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">&#x27;fab fa-apple&#x27;</span>&gt;</span></span><span class="language-xml"><span class="tag">&lt;/<span class="name">i</span>&gt;</span></span></span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">b</span>&gt;</span></span>心率管家<span class="language-xml"><span class="tag">&lt;/<span class="name">b</span>&gt;</span></span></span><br><span class="line">  &#123;% p red, 专业版 %&#125;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&#x27;https://cdn.jsdelivr.net/gh/fomalhaut1998/cdn-assets/qrcode/heartmate_pro.png&#x27;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">a</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&#x27;https://apps.apple.com/cn/app/heart-mate-lite-hrm-utility/id1475747930?ls=1&#x27;</span>&gt;</span></span></span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">&#x27;fab fa-apple&#x27;</span>&gt;</span></span><span class="language-xml"><span class="tag">&lt;/<span class="name">i</span>&gt;</span></span></span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">b</span>&gt;</span></span>心率管家<span class="language-xml"><span class="tag">&lt;/<span class="name">b</span>&gt;</span></span></span><br><span class="line">  &#123;% p green, 免费版 %&#125;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&#x27;https://cdn.jsdelivr.net/gh/fomalhaut1998/cdn-assets/qrcode/heartmate_lite.png&#x27;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">a</span>&gt;</span></span></span><br><span class="line">&#123;% endbtns %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><p>1.如果需要显示类似「团队成员」之类的一组含有头像的链接</p><div class="btns circle grid5">            <a class="button" href='https://xaoxuu.com' title='xaoxuu'><img src='https://cdn.jsdelivr.net/gh/xaoxuu/cdn-assets/avatar/avatar.png'>xaoxuu</a><a class="button" href='https://xaoxuu.com' title='xaoxuu'><img src='https://cdn.jsdelivr.net/gh/xaoxuu/cdn-assets/avatar/avatar.png'>xaoxuu</a><a class="button" href='https://xaoxuu.com' title='xaoxuu'><img src='https://cdn.jsdelivr.net/gh/xaoxuu/cdn-assets/avatar/avatar.png'>xaoxuu</a><a class="button" href='https://xaoxuu.com' title='xaoxuu'><img src='https://cdn.jsdelivr.net/gh/xaoxuu/cdn-assets/avatar/avatar.png'>xaoxuu</a><a class="button" href='https://xaoxuu.com' title='xaoxuu'><img src='https://cdn.jsdelivr.net/gh/xaoxuu/cdn-assets/avatar/avatar.png'>xaoxuu</a>          </div>2.或者含有图标的按钮<div class="btns rounded grid5">            <a class="button" href='/' title='下载源码'><i class='fas fa-download'></i>下载源码</a><a class="button" href='/' title='查看文档'><i class='fas fa-book-open'></i>查看文档</a>          </div>3.圆形图标 + 标题 + 描述 + 图片 + 网格5列 + 居中<div class="btns circle center grid5">            <a href='https://apps.apple.com/cn/app/heart-mate-pro-hrm-utility/id1463348922?ls=1'>  <i class='fab fa-apple'></i>  <b>心率管家</b>  <p class='p red'>专业版</p>  <img src='https://cdn.jsdelivr.net/gh/fomalhaut1998/cdn-assets/qrcode/heartmate_pro.png'></a><a href='https://apps.apple.com/cn/app/heart-mate-lite-hrm-utility/id1475747930?ls=1'>  <i class='fab fa-apple'></i>  <b>心率管家</b>  <p class='p green'>免费版</p>  <img src='https://cdn.jsdelivr.net/gh/fomalhaut1998/cdn-assets/qrcode/heartmate_lite.png'></a>          </div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-12-github卡片-ghcard">2.12 github卡片 ghcard</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">参数配置</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#123;% ghcard 用户名, 其它参数（可选） %&#125;</span><br><span class="line">&#123;% ghcard 用户名/仓库, 其它参数（可选） %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><p>使用<code>,</code>分割各个参数。写法为：<code>参数名=参数值</code><br>以下只写几个常用参数值。</p><table><thead><tr><th><strong>参数名</strong></th><th>取值</th><th>释义</th></tr></thead><tbody><tr><td>hide</td><td>stars,commits,prs,issues,contribs</td><td>隐藏指定统计</td></tr><tr><td>count_private</td><td>true</td><td>将私人项目贡献添加到总提交计数中</td></tr><tr><td>show_icons</td><td>true</td><td>显示图标</td></tr><tr><td>theme</td><td>查阅:<a href="https://github.com/anuraghazra/github-readme-stats/blob/master/themes/README.md">Available Themes</a></td><td>主题</td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><p>1.用户信息卡片</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">| &#123;% ghcard fomalhaut1998 %&#125; | &#123;% ghcard fomalhaut1998, theme=vue %&#125; |</span><br><span class="line">| -- | -- |</span><br><span class="line">| &#123;% ghcard fomalhaut1998, theme=buefy %&#125; | &#123;% ghcard fomalhaut1998, theme=solarized-light %&#125; |</span><br><span class="line">| &#123;% ghcard fomalhaut1998, theme=onedark %&#125; | &#123;% ghcard fomalhaut1998, theme=solarized-dark %&#125; |</span><br><span class="line">| &#123;% ghcard fomalhaut1998, theme=algolia %&#125; | &#123;% ghcard fomalhaut1998, theme=calm %&#125; |</span><br></pre></td></tr></table></figure><p>2.仓库信息卡片</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">| &#123;% ghcard volantis-x/hexo-theme-volantis %&#125; | &#123;% ghcard volantis-x/hexo-theme-volantis, theme=vue %&#125; |</span><br><span class="line">| -- | -- |</span><br><span class="line">| &#123;% ghcard volantis-x/hexo-theme-volantis, theme=buefy %&#125; | &#123;% ghcard volantis-x/hexo-theme-volantis, theme=solarized-light %&#125; |</span><br><span class="line">| &#123;% ghcard volantis-x/hexo-theme-volantis, theme=onedark %&#125; | &#123;% ghcard volantis-x/hexo-theme-volantis, theme=solarized-dark %&#125; |</span><br><span class="line">| &#123;% ghcard volantis-x/hexo-theme-volantis, theme=algolia %&#125; | &#123;% ghcard volantis-x/hexo-theme-volantis, theme=calm %&#125; |</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><p>1.用户信息卡片</p><table><thead><tr><th><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/fomalhaut1998"><img src="https://github-readme-stats.vercel.app/api/?username=fomalhaut1998&show_owner=true"/></a></th><th><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/fomalhaut1998"><img src="https://github-readme-stats.vercel.app/api/?username=fomalhaut1998&theme=vue&show_owner=true"/></a></th></tr></thead><tbody><tr><td><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/fomalhaut1998"><img src="https://github-readme-stats.vercel.app/api/?username=fomalhaut1998&theme=buefy&show_owner=true"/></a></td><td><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/fomalhaut1998"><img src="https://github-readme-stats.vercel.app/api/?username=fomalhaut1998&theme=solarized-light&show_owner=true"/></a></td></tr><tr><td><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/fomalhaut1998"><img src="https://github-readme-stats.vercel.app/api/?username=fomalhaut1998&theme=onedark&show_owner=true"/></a></td><td><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/fomalhaut1998"><img src="https://github-readme-stats.vercel.app/api/?username=fomalhaut1998&theme=solarized-dark&show_owner=true"/></a></td></tr><tr><td><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/fomalhaut1998"><img src="https://github-readme-stats.vercel.app/api/?username=fomalhaut1998&theme=algolia&show_owner=true"/></a></td><td><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/fomalhaut1998"><img src="https://github-readme-stats.vercel.app/api/?username=fomalhaut1998&theme=calm&show_owner=true"/></a></td></tr></tbody></table><p>2.仓库信息卡片</p><table><thead><tr><th><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/volantis-x/hexo-theme-volantis"><img src="https://github-readme-stats.vercel.app/api/pin/?username=volantis-x&repo=hexo-theme-volantis&show_owner=true"/></a></th><th><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/volantis-x/hexo-theme-volantis"><img src="https://github-readme-stats.vercel.app/api/pin/?username=volantis-x&repo=hexo-theme-volantis&theme=vue&show_owner=true"/></a></th></tr></thead><tbody><tr><td><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/volantis-x/hexo-theme-volantis"><img src="https://github-readme-stats.vercel.app/api/pin/?username=volantis-x&repo=hexo-theme-volantis&theme=buefy&show_owner=true"/></a></td><td><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/volantis-x/hexo-theme-volantis"><img src="https://github-readme-stats.vercel.app/api/pin/?username=volantis-x&repo=hexo-theme-volantis&theme=solarized-light&show_owner=true"/></a></td></tr><tr><td><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/volantis-x/hexo-theme-volantis"><img src="https://github-readme-stats.vercel.app/api/pin/?username=volantis-x&repo=hexo-theme-volantis&theme=onedark&show_owner=true"/></a></td><td><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/volantis-x/hexo-theme-volantis"><img src="https://github-readme-stats.vercel.app/api/pin/?username=volantis-x&repo=hexo-theme-volantis&theme=solarized-dark&show_owner=true"/></a></td></tr><tr><td><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/volantis-x/hexo-theme-volantis"><img src="https://github-readme-stats.vercel.app/api/pin/?username=volantis-x&repo=hexo-theme-volantis&theme=algolia&show_owner=true"/></a></td><td><a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/volantis-x/hexo-theme-volantis"><img src="https://github-readme-stats.vercel.app/api/pin/?username=volantis-x&repo=hexo-theme-volantis&theme=calm&show_owner=true"/></a></td></tr></tbody></table><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-13-github徽标-ghbdage">2.13 github徽标 ghbdage</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">配置参数</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% bdage [right],[left],[logo]||[color],[link],[title]||[option] %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><code>left</code>：徽标左边的信息，必选参数。</li><li><code>right</code>: 徽标右边的信息，必选参数，</li><li><code>logo</code>：徽标图标，图标名称详见<a href="https://simpleicons.org/">simpleicons</a>，可选参数。</li><li><code>color</code>：徽标右边的颜色，可选参数。</li><li><code>link</code>：指向的链接，可选参数。</li><li><code>title</code>：徽标的额外信息，可选参数。主要用于优化SEO，但<code>object</code>标签不会像<code>a</code>标签一样在鼠标悬停显示<code>title</code>信息。</li><li><code>option</code>：自定义参数，支持<a href="https://shields.io/">shields.io</a>的全部API参数支持，具体参数可以参看上文中的拓展写法示例。形式为<code>name1=value2&amp;name2=value2</code>。</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><p>1.基本参数,定义徽标左右文字和图标</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#123;% bdage Theme,Butterfly %&#125;</span><br><span class="line">&#123;% bdage Frame,Hexo,hexo %&#125;</span><br></pre></td></tr></table></figure><p>2.信息参数，定义徽标右侧内容背景色，指向链接</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;% bdage CDN,JsDelivr,jsDelivr||abcdef,https://metroui.org.ua/index.html,本站使用JsDelivr为静态资源提供CDN加速 %&#125;</span><br><span class="line">//如果是跨顺序省略可选参数，仍然需要写个逗号,用作分割</span><br><span class="line">&#123;% bdage Source,GitHub,GitHub||,https://github.com/ %&#125;</span><br></pre></td></tr></table></figure><p>3.拓展参数，支持shields的API的全部参数内容</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;% bdage Hosted,Vercel,Vercel||brightgreen,https://vercel.com/,本站采用双线部署，默认线路托管于Vercel||style=social&amp;logoWidth=20 %&#125;</span><br><span class="line">//如果是跨顺序省略可选参数组，仍然需要写双竖线||用作分割</span><br><span class="line">&#123;% bdage Hosted,Vercel,Vercel||||style=social&amp;logoWidth=20&amp;logoColor=violet %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><p>1.基本参数,定义徽标左右文字和图标</p><p><object class="ghbdage" style="margin-inline:5px" title="" standby="loading..." data="https://img.shields.io/badge/Butterfly-Theme-orange?logo=&color=orange&link=&"></object><br><object class="ghbdage" style="margin-inline:5px" title="" standby="loading..." data="https://img.shields.io/badge/Hexo-Frame-orange?logo=hexo&color=orange&link=&"></object></p><p>2.信息参数，定义徽标右侧内容背景色，指向链接</p><p><object class="ghbdage" style="margin-inline:5px" title="本站使用JsDelivr为静态资源提供CDN加速" standby="loading..." data="https://img.shields.io/badge/JsDelivr-CDN-orange?logo=jsDelivr&color=abcdef&link=https://metroui.org.ua/index.html&"></object><br>//如果是跨顺序省略可选参数，仍然需要写个逗号,用作分割<br><object class="ghbdage" style="margin-inline:5px" title="" standby="loading..." data="https://img.shields.io/badge/GitHub-Source-orange?logo=GitHub&color=orange&link=https://github.com/&"></object></p><p>3.拓展参数，支持shields的API的全部参数内容</p><p><object class="ghbdage" style="margin-inline:5px" title="本站采用双线部署，默认线路托管于Vercel" standby="loading..." data="https://img.shields.io/badge/Vercel-Hosted-orange?logo=Vercel&color=brightgreen&link=https://vercel.com/&style=social&logoWidth=20"></object><br>//如果是跨顺序省略可选参数组，仍然需要写双竖线||用作分割<br><object class="ghbdage" style="margin-inline:5px" title="" standby="loading..." data="https://img.shields.io/badge/Vercel-Hosted-orange?logo=Vercel&color=orange&link=&style=social&logoWidth=20&logoColor=violet"></object></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-14-网站卡片-sites">2.14 网站卡片 sites</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-3">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;% sitegroup %&#125;</span><br><span class="line">&#123;% site 标题, url=链接, screenshot=截图链接, avatar=头像链接（可选）, description=描述（可选） %&#125;</span><br><span class="line">&#123;% site 标题, url=链接, screenshot=截图链接, avatar=头像链接（可选）, description=描述（可选） %&#125;</span><br><span class="line">&#123;% endsitegroup %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#123;% sitegroup %&#125;</span><br><span class="line">&#123;% site xaoxuu, url=https://xaoxuu.com, screenshot=https://i.loli.net/2020/08/21/VuSwWZ1xAeUHEBC.jpg, avatar=https://cdn.jsdelivr.net/gh/fomalhaut1998/cdn-assets/avatar/avatar.png, description=简约风格 %&#125;</span><br><span class="line">&#123;% site inkss, url=https://inkss.cn, screenshot=https://i.loli.net/2020/08/21/Vzbu3i8fXs6Nh5Y.jpg, avatar=https://cdn.jsdelivr.net/gh/inkss/common@master/static/web/avatar.jpg, description=这是一段关于这个网站的描述文字 %&#125;</span><br><span class="line">&#123;% site MHuiG, url=https://blog.mhuig.top, screenshot=https://i.loli.net/2020/08/22/d24zpPlhLYWX6D1.png, avatar=https://cdn.jsdelivr.net/gh/MHuiG/imgbed@master/data/p.png, description=这是一段关于这个网站的描述文字 %&#125;</span><br><span class="line">&#123;% site Colsrch, url=https://colsrch.top, screenshot=https://i.loli.net/2020/08/22/dFRWXm52OVu8qfK.png, avatar=https://cdn.jsdelivr.net/gh/Colsrch/images/Colsrch/avatar.jpg, description=这是一段关于这个网站的描述文字 %&#125;</span><br><span class="line">&#123;% site Linhk1606, url=https://linhk1606.github.io, screenshot=https://i.loli.net/2020/08/21/3PmGLCKicnfow1x.png, avatar=https://i.loli.net/2020/02/09/PN7I5RJfFtA93r2.png, description=这是一段关于这个网站的描述文字 %&#125;</span><br><span class="line">&#123;% endsitegroup %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><div class="site-card-group"><a class="site-card" href="https://fomalhaut1998.com"><div class="img"><img src="https://i.loli.net/2020/08/21/VuSwWZ1xAeUHEBC.jpg"/></div><div class="info"><img src="https://cdn.jsdelivr.net/gh/fomalhaut1998/cdn-assets/avatar/avatar.png"/><span class="title">fomalhaut1998</span><span class="desc">简约风格</span></div></a><a class="site-card" href="https://inkss.cn"><div class="img"><img src="https://i.loli.net/2020/08/21/Vzbu3i8fXs6Nh5Y.jpg"/></div><div class="info"><img src="https://cdn.jsdelivr.net/gh/inkss/common@master/static/web/avatar.jpg"/><span class="title">inkss</span><span class="desc">这是一段关于这个网站的描述文字</span></div></a><a class="site-card" href="https://blog.mhuig.top"><div class="img"><img src="https://i.loli.net/2020/08/22/d24zpPlhLYWX6D1.png"/></div><div class="info"><img src="https://cdn.jsdelivr.net/gh/MHuiG/imgbed@master/data/p.png"/><span class="title">MHuiG</span><span class="desc">这是一段关于这个网站的描述文字</span></div></a><a class="site-card" href="https://colsrch.top"><div class="img"><img src="https://i.loli.net/2020/08/22/dFRWXm52OVu8qfK.png"/></div><div class="info"><img src="https://cdn.jsdelivr.net/gh/Colsrch/images/Colsrch/avatar.jpg"/><span class="title">Colsrch</span><span class="desc">这是一段关于这个网站的描述文字</span></div></a><a class="site-card" href="https://linhk1606.github.io"><div class="img"><img src="https://i.loli.net/2020/08/21/3PmGLCKicnfow1x.png"/></div><div class="info"><img src="https://i.loli.net/2020/02/09/PN7I5RJfFtA93r2.png"/><span class="title">Linhk1606</span><span class="desc">这是一段关于这个网站的描述文字</span></div></a></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-15-行内图片-inlineimage">2.15 行内图片 inlineimage</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">参数配置</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% inlineimage 图片链接, height=高度（可选） %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><code>高度</code>：height=20px</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">这是 &#123;% inlineimage https://cdn.jsdelivr.net/gh/volantis-x/cdn-emoji/aru-l/0000.gif %&#125; 一段话。</span><br><span class="line"></span><br><span class="line">这又是 &#123;% inlineimage https://cdn.jsdelivr.net/gh/volantis-x/cdn-emoji/aru-l/5150.gif, height=40px %&#125; 一段话。</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><p>这是 <img no-lazy class="inline" src="https://cdn.jsdelivr.net/gh/volantis-x/cdn-emoji/aru-l/0000.gif" style="height:1.5em"/> 一段话。</p><p>这又是 <img no-lazy class="inline" src="https://cdn.jsdelivr.net/gh/volantis-x/cdn-emoji/aru-l/5150.gif" style="height:40px;"/> 一段话。</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-16-单张图片-image">2.16 单张图片 image</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">参数配置</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% image 链接, width=宽度（可选）, height=高度（可选）, alt=描述（可选）, bg=占位颜色（可选） %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li>图片宽度高度：width=300px, height=32px</li><li>图片描述：alt=图片描述（butterfly需要在主题配置文件中开启图片描述）</li><li>占位背景色：bg=#f2f2f2</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><p>1.添加描述：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% image https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/025.jpg, alt=每天下课回宿舍的路，没有什么故事。 %&#125;</span><br></pre></td></tr></table></figure><p>2.指定宽度</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% image https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/025.jpg, width=400px %&#125;</span><br></pre></td></tr></table></figure><p>3.指定宽度并添加描述：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% image https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/025.jpg, width=400px, alt=每天下课回宿舍的路，没有什么故事。 %&#125;</span><br></pre></td></tr></table></figure><p>4.设置占位背景色：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% image https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/025.jpg, width=400px, bg=#1D0C04, alt=优化不同宽度浏览的观感 %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><p>1.添加描述：</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/025.jpg" alt="每天下课回宿舍的路，没有什么故事。"/></div><span class="image-caption">每天下课回宿舍的路，没有什么故事。</span></div>2..指定宽度<div class="img-wrap"><div class="img-bg"><img class="img" src="https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/025.jpg" style="width:400px;"/></div></div>3.指定宽度并添加描述：<div class="img-wrap"><div class="img-bg"><img class="img" src="https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/025.jpg" alt="每天下课回宿舍的路，没有什么故事。" style="width:400px;"/></div><span class="image-caption">每天下课回宿舍的路，没有什么故事。</span></div>4.设置占位背景色：<div class="img-wrap"><div class="img-bg" style="background:#1D0C04"><img class="img" src="https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/025.jpg" alt="优化不同宽度浏览的观感" style="width:400px;"/></div><span class="image-caption">优化不同宽度浏览的观感</span></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-17-音频-audio">2.17 音频 audio</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-3">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% audio 音频链接 %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% audio https://github.com/volantis-x/volantis-docs/releases/download/assets/Lumia1020.mp3 %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><div class="audio"><audio controls preload><source src='https://github.com/volantis-x/volantis-docs/releases/download/assets/Lumia1020.mp3' type='audio/mp3'>Your browser does not support the audio tag.</audio></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-18-视频-video">2.18 视频 video</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% video 视频链接 %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><code>对齐方向</code>：left, center, right</li><li><code>列数</code>：逗号后面直接写列数，支持 1 ～ 4 列。</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><p>1.100%宽度</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% video https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG<span class="emphasis">_0341.mov %&#125;</span></span><br></pre></td></tr></table></figure><p>2.50%宽度</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;% videos, 2 %&#125;</span><br><span class="line">&#123;% video https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG<span class="emphasis">_0341.mov %&#125;</span></span><br><span class="line"><span class="emphasis">&#123;% video https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_</span>0341.mov %&#125;</span><br><span class="line">&#123;% video https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG<span class="emphasis">_0341.mov %&#125;</span></span><br><span class="line"><span class="emphasis">&#123;% video https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_</span>0341.mov %&#125;</span><br><span class="line">&#123;% endvideos %&#125;</span><br></pre></td></tr></table></figure><p>3.25%宽度</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&#123;% videos, 4 %&#125;</span><br><span class="line">&#123;% video https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG<span class="emphasis">_0341.mov %&#125;</span></span><br><span class="line"><span class="emphasis">&#123;% video https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_</span>0341.mov %&#125;</span><br><span class="line">&#123;% video https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG<span class="emphasis">_0341.mov %&#125;</span></span><br><span class="line"><span class="emphasis">&#123;% video https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_</span>0341.mov %&#125;</span><br><span class="line">&#123;% video https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG<span class="emphasis">_0341.mov %&#125;</span></span><br><span class="line"><span class="emphasis">&#123;% video https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_</span>0341.mov %&#125;</span><br><span class="line">&#123;% video https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG<span class="emphasis">_0341.mov %&#125;</span></span><br><span class="line"><span class="emphasis">&#123;% video https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_</span>0341.mov %&#125;</span><br><span class="line">&#123;% endvideos %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><p>1.100%宽度</p><div class="video"><video controls preload><source src='https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_0341.mov' type='video/mp4'>Your browser does not support the video tag.</video></div>2.50%宽度<div class="videos" col='2'><div class="video"><video controls preload><source src='https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_0341.mov' type='video/mp4'>Your browser does not support the video tag.</video></div><div class="video"><video controls preload><source src='https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_0341.mov' type='video/mp4'>Your browser does not support the video tag.</video></div><div class="video"><video controls preload><source src='https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_0341.mov' type='video/mp4'>Your browser does not support the video tag.</video></div><div class="video"><video controls preload><source src='https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_0341.mov' type='video/mp4'>Your browser does not support the video tag.</video></div></div>3.25%宽度<div class="videos" col='4'><div class="video"><video controls preload><source src='https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_0341.mov' type='video/mp4'>Your browser does not support the video tag.</video></div><div class="video"><video controls preload><source src='https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_0341.mov' type='video/mp4'>Your browser does not support the video tag.</video></div><div class="video"><video controls preload><source src='https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_0341.mov' type='video/mp4'>Your browser does not support the video tag.</video></div><div class="video"><video controls preload><source src='https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_0341.mov' type='video/mp4'>Your browser does not support the video tag.</video></div><div class="video"><video controls preload><source src='https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_0341.mov' type='video/mp4'>Your browser does not support the video tag.</video></div><div class="video"><video controls preload><source src='https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_0341.mov' type='video/mp4'>Your browser does not support the video tag.</video></div><div class="video"><video controls preload><source src='https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_0341.mov' type='video/mp4'>Your browser does not support the video tag.</video></div><div class="video"><video controls preload><source src='https://github.com/volantis-x/volantis-docs/releases/download/assets/IMG_0341.mov' type='video/mp4'>Your browser does not support the video tag.</video></div></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-19-相册-gallery">2.19 相册 gallery</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">参数配置</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><p>1.gallerygroup 相册图库</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;gallery-group-main&quot;</span>&gt;</span></span></span><br><span class="line">&#123;% galleryGroup name description link img-url %&#125;</span><br><span class="line">&#123;% galleryGroup name description link img-url %&#125;</span><br><span class="line">&#123;% galleryGroup name description link img-url %&#125;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br></pre></td></tr></table></figure><p>2.gallery 相册</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;% gallery %&#125;</span><br><span class="line">markdown 圖片格式</span><br><span class="line">&#123;% endgallery %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ul><li>gallerygroup 相册图库</li></ul><table><thead><tr><th>参数名</th><th>释义</th></tr></thead><tbody><tr><td>name</td><td>图库名字</td></tr><tr><td>description</td><td>图库描述</td></tr><tr><td>link</td><td>链接到对应相册的地址</td></tr><tr><td>img-url</td><td>图库封面</td></tr></tbody></table><ul><li><p>gallery 相册</p><p>区别于旧版的Gallery相册,新的Gallery相册会自动根据图片长度进行排版，书写也更加方便，与markdown格式一样。可根据需要插入到相应的md。无需再自己配置长宽。<strong>建议在粘贴时故意使用长短、大小、横竖不一的图片</strong>，会有更好的效果。（尺寸完全相同的图片只会平铺输出，效果很糟糕）</p></li></ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><p>1.gallerygroup 相册图库</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;gallery-group-main&quot;</span>&gt;</span></span></span><br><span class="line">&#123;% galleryGroup MC 在Rikkaの六花服务器里留下的足迹 &#x27;/gallery/MC/&#x27; https://cdn.cbd.int/akilar-candyassets@1.0.36/image/1.jpg %&#125;</span><br><span class="line">&#123;% galleryGroup Gundam 哦咧哇gundam哒！ &#x27;/gallery/Gundam/&#x27; https://cdn.cbd.int/akilar-candyassets@1.0.36/image/20200907110508327.png %&#125;</span><br><span class="line">&#123;% galleryGroup I-am-Akilar 某种意义上也算自拍吧 &#x27;/gallery/I-am-Akilar/&#x27; https://cdn.cbd.int/akilar-candyassets@1.0.36/image/20200907113116651.png %&#125;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br></pre></td></tr></table></figure><p>2.gallery 相册</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;% gallery %&#125;</span><br><span class="line">![](<span class="link">https://i.loli.net/2019/12/25/Fze9jchtnyJXMHN.jpg</span>)</span><br><span class="line">![](<span class="link">https://i.loli.net/2019/12/25/ryLVePaqkYm4TEK.jpg</span>)</span><br><span class="line">&#123;% endgallery %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><p>1.gallerygroup 相册图库</p><div class="gallery-group-main">  <figure class="gallery-group">  <img class="gallery-group-img no-lightbox" src='https://cdn.cbd.int/akilar-candyassets@1.0.36/image/1.jpg' alt="Group Image Gallery">  <figcaption>  <div class="gallery-group-name">MC</div>  <p>在Rikkaの六花服务器里留下的足迹</p>  <a href='/gallery/MC/'></a>  </figcaption>  </figure>  <figure class="gallery-group">  <img class="gallery-group-img no-lightbox" src='https://cdn.cbd.int/akilar-candyassets@1.0.36/image/20200907110508327.png' alt="Group Image Gallery">  <figcaption>  <div class="gallery-group-name">Gundam</div>  <p>哦咧哇gundam哒！</p>  <a href='/gallery/Gundam/'></a>  </figcaption>  </figure>  <figure class="gallery-group">  <img class="gallery-group-img no-lightbox" src='https://cdn.cbd.int/akilar-candyassets@1.0.36/image/20200907113116651.png' alt="Group Image Gallery">  <figcaption>  <div class="gallery-group-name">I-am-Akilar</div>  <p>某种意义上也算自拍吧</p>  <a href='/gallery/I-am-Akilar/'></a>  </figcaption>  </figure></div>2.gallery 相册<div class="fj-gallery"><p><img src="https://i.loli.net/2019/12/25/Fze9jchtnyJXMHN.jpg" alt=""><br><img src="https://i.loli.net/2019/12/25/ryLVePaqkYm4TEK.jpg" alt=""></p>          </div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-20-折叠框-folding">2.20 折叠框 folding</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-3">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><p>1.gallerygroup 相册图库</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;% folding 参数（可选）, 标题 %&#125;</span><br><span class="line">![](<span class="link">https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper/abstract/41F215B9-261F-48B4-80B5-4E86E165259E.jpeg</span>)</span><br><span class="line">&#123;% endfolding %&#125;</span><br></pre></td></tr></table></figure><!-- tab 参数配置 --><ol><li><p><code>颜色</code>：blue, cyan, green, yellow, red</p></li><li><p><code>状态</code>：状态填写 open 代表默认打开。</p></li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">&#123;% folding 查看图片测试 %&#125;</span><br><span class="line"></span><br><span class="line">![](<span class="link">https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper/abstract/41F215B9-261F-48B4-80B5-4E86E165259E.jpeg</span>)</span><br><span class="line"></span><br><span class="line">&#123;% endfolding %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% folding cyan open, 查看默认打开的折叠框 %&#125;</span><br><span class="line"></span><br><span class="line">这是一个默认打开的折叠框。</span><br><span class="line"></span><br><span class="line">&#123;% endfolding %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% folding green, 查看代码测试 %&#125;</span><br><span class="line">假装这里有代码块（代码块没法嵌套代码块）</span><br><span class="line">&#123;% endfolding %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% folding yellow, 查看列表测试 %&#125;</span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> haha</span><br><span class="line"><span class="bullet">-</span> hehe</span><br><span class="line"></span><br><span class="line">&#123;% endfolding %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% folding red, 查看嵌套测试 %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% folding blue, 查看嵌套测试2 %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% folding 查看嵌套测试3 %&#125;</span><br><span class="line"></span><br><span class="line">hahaha <span class="language-xml"><span class="tag">&lt;<span class="name">span</span>&gt;</span></span><span class="language-xml"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&#x27;https://cdn.jsdelivr.net/gh/volantis-x/cdn-emoji/tieba/%E6%BB%91%E7%A8%BD.png&#x27;</span> <span class="attr">style</span>=<span class="string">&#x27;height:24px&#x27;</span>&gt;</span></span><span class="language-xml"><span class="tag">&lt;/<span class="name">span</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line">&#123;% endfolding %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% endfolding %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% endfolding %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><details class="folding-tag" ><summary> 查看图片测试 </summary>              <div class='content'>              <p><img src="https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper/abstract/41F215B9-261F-48B4-80B5-4E86E165259E.jpeg" alt=""></p>              </div>            </details><details class="folding-tag" cyan open><summary> 查看默认打开的折叠框 </summary>              <div class='content'>              <p>这是一个默认打开的折叠框。</p>              </div>            </details><details class="folding-tag" green><summary> 查看代码测试 </summary>              <div class='content'>              <p>假装这里有代码块（代码块没法嵌套代码块）</p>              </div>            </details><details class="folding-tag" yellow><summary> 查看列表测试 </summary>              <div class='content'>              <ul><li>haha</li><li>hehe</li></ul>              </div>            </details><details class="folding-tag" red><summary> 查看嵌套测试 </summary>              <div class='content'>              <details class="folding-tag" blue><summary> 查看嵌套测试2 </summary>              <div class='content'>              <details class="folding-tag" ><summary> 查看嵌套测试3 </summary>              <div class='content'>              <p>hahaha <span><img src='https://cdn.jsdelivr.net/gh/volantis-x/cdn-emoji/tieba/%E6%BB%91%E7%A8%BD.png' style='height:24px'></span></p>              </div>            </details>              </div>            </details>              </div>            </details><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-21-分栏-tab">2.21 分栏 tab</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">配置参数</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;% tabs Unique name, [index] %&#125;</span><br><span class="line">&lt;!-- tab [Tab caption] [@icon] --&gt;</span><br><span class="line">Any content (support inline tags too).</span><br><span class="line">&lt;!-- endtab --&gt;</span><br><span class="line">&#123;% endtabs %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><p>Unique name :</p><ul><li><p>选项卡块标签的唯一名称，不带逗号。</p></li><li><p>将在#id中用作每个标签及其索引号的前缀。</p></li><li><p>如果名称中包含空格，则对于生成#id，所有空格将由破折号代替。</p></li><li><p>仅当前帖子/页面的URL必须是唯一的！</p></li></ul></li><li><p>[index]:</p><ul><li><p>活动选项卡的索引号。</p></li><li><p>如果未指定，将选择第一个标签（1）。</p></li><li><p>如果index为-1，则不会选择任何选项卡。</p></li><li><p>可选参数。</p></li></ul></li><li><p>[Tab caption]:</p><ul><li><p>当前选项卡的标题。</p></li><li><p>如果未指定标题，则带有制表符索引后缀的唯一名称将用作制表符的标题。</p></li><li><p>如果未指定标题，但指定了图标，则标题将为空。</p></li><li><p>可选参数。</p></li></ul></li><li><p>[@icon]:</p><ul><li><p>FontAwesome图标名称（全名，看起来像“ fas fa-font”）</p></li><li><p>可以指定带空格或不带空格；</p></li><li><p>例如’Tab caption @icon’ 和 ‘Tab caption@icon’.</p></li><li><p>可选参数。</p></li></ul></li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><p>1.Demo 1 - 预设选择第一个【默认】</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;% tabs test1 %&#125;</span><br><span class="line">&lt;!-- tab --&gt;</span><br><span class="line"><span class="strong">**This is Tab 1.**</span></span><br><span class="line">&lt;!-- endtab --&gt;</span><br><span class="line"></span><br><span class="line">&lt;!-- tab --&gt;</span><br><span class="line"><span class="strong">**This is Tab 2.**</span></span><br><span class="line">&lt;!-- endtab --&gt;</span><br><span class="line"></span><br><span class="line">&lt;!-- tab --&gt;</span><br><span class="line"><span class="strong">**This is Tab 3.**</span></span><br><span class="line">&lt;!-- endtab --&gt;</span><br><span class="line">&#123;% endtabs %&#125;</span><br></pre></td></tr></table></figure><p>2.Demo 2 - 预设选择tabs</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;% tabs test2, 3 %&#125;</span><br><span class="line">&lt;!-- tab --&gt;</span><br><span class="line"><span class="strong">**This is Tab 1.**</span></span><br><span class="line">&lt;!-- endtab --&gt;</span><br><span class="line"></span><br><span class="line">&lt;!-- tab --&gt;</span><br><span class="line"><span class="strong">**This is Tab 2.**</span></span><br><span class="line">&lt;!-- endtab --&gt;</span><br><span class="line"></span><br><span class="line">&lt;!-- tab --&gt;</span><br><span class="line"><span class="strong">**This is Tab 3.**</span></span><br><span class="line">&lt;!-- endtab --&gt;</span><br><span class="line">&#123;% endtabs %&#125;</span><br></pre></td></tr></table></figure><p>3.Demo 3 - 没有预设值</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;% tabs test3, -1 %&#125;</span><br><span class="line">&lt;!-- tab --&gt;</span><br><span class="line"><span class="strong">**This is Tab 1.**</span></span><br><span class="line">&lt;!-- endtab --&gt;</span><br><span class="line"></span><br><span class="line">&lt;!-- tab --&gt;</span><br><span class="line"><span class="strong">**This is Tab 2.**</span></span><br><span class="line">&lt;!-- endtab --&gt;</span><br><span class="line"></span><br><span class="line">&lt;!-- tab --&gt;</span><br><span class="line"><span class="strong">**This is Tab 3.**</span></span><br><span class="line">&lt;!-- endtab --&gt;</span><br><span class="line">&#123;% endtabs %&#125;</span><br></pre></td></tr></table></figure><p>4.Demo 4 - 自定义Tab名 + 只有icon + icon和Tab名</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;% tabs test4 %&#125;</span><br><span class="line">&lt;!-- tab 第一个Tab --&gt;</span><br><span class="line"><span class="strong">**tab名字为第一个Tab**</span></span><br><span class="line">&lt;!-- endtab --&gt;</span><br><span class="line"></span><br><span class="line">&lt;!-- tab @fab fa-apple-pay --&gt;</span><br><span class="line"><span class="strong">**只有图标 没有Tab名字**</span></span><br><span class="line">&lt;!-- endtab --&gt;</span><br><span class="line"></span><br><span class="line">&lt;!-- tab 炸弹@fas fa-bomb --&gt;</span><br><span class="line"><span class="strong">**名字+icon**</span></span><br><span class="line">&lt;!-- endtab --&gt;</span><br><span class="line">&#123;% endtabs %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><p>1.Demo 1 - 预设选择第一个【默认】</p><div class="tabs" id="test1"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#test1-1">test1 1</button></li><li class="tab"><button type="button" data-href="#test1-2">test1 2</button></li><li class="tab"><button type="button" data-href="#test1-3">test1 3</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="test1-1"><p><strong>This is Tab 1.</strong></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="test1-2"><p><strong>This is Tab 2.</strong></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="test1-3"><p><strong>This is Tab 3.</strong></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p>2.Demo 2 - 预设选择tabs</p><div class="tabs" id="test2"><ul class="nav-tabs"><li class="tab"><button type="button" data-href="#test2-1">test2 1</button></li><li class="tab"><button type="button" data-href="#test2-2">test2 2</button></li><li class="tab active"><button type="button" data-href="#test2-3">test2 3</button></li></ul><div class="tab-contents"><div class="tab-item-content" id="test2-1"><p><strong>This is Tab 1.</strong></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="test2-2"><p><strong>This is Tab 2.</strong></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content active" id="test2-3"><p><strong>This is Tab 3.</strong></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p>3.Demo 3 - 没有预设值</p><div class="tabs" id="test3"><ul class="nav-tabs"><li class="tab"><button type="button" data-href="#test3-1">test3 1</button></li><li class="tab"><button type="button" data-href="#test3-2">test3 2</button></li><li class="tab"><button type="button" data-href="#test3-3">test3 3</button></li></ul><div class="tab-contents"><div class="tab-item-content" id="test3-1"><p><strong>This is Tab 1.</strong></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="test3-2"><p><strong>This is Tab 2.</strong></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="test3-3"><p><strong>This is Tab 3.</strong></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><p>4.Demo 4 - 自定义Tab名 + 只有icon + icon和Tab名</p><div class="tabs" id="test4"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#test4-1">第一个Tab</button></li><li class="tab"><button type="button" data-href="#test4-2"><i class="fab fa-apple-pay" style="text-align: center;"></i></button></li><li class="tab"><button type="button" data-href="#test4-3"><i class="fas fa-bomb"></i>炸弹</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="test4-1"><p><strong>tab名字为第一个Tab</strong></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="test4-2"><p><strong>只有图标 没有Tab名字</strong></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="test4-3"><p><strong>名字+icon</strong></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-22-诗词标签-poem">2.22 诗词标签 poem</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">参数配置</button></li><li class="tab"><button type="button" data-href="#分栏-2">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-3">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><ol><li><code>title</code>：诗词标题</li><li><code>author</code>：作者，可以不写</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&#123;% poem 水调歌头,苏轼 %&#125;</span><br><span class="line">丙辰中秋，欢饮达旦，大醉，作此篇，兼怀子由。</span><br><span class="line">明月几时有？把酒问青天。</span><br><span class="line">不知天上宫阙，今夕是何年？</span><br><span class="line">我欲乘风归去，又恐琼楼玉宇，高处不胜寒。</span><br><span class="line">起舞弄清影，何似在人间？</span><br><span class="line"></span><br><span class="line">转朱阁，低绮户，照无眠。</span><br><span class="line">不应有恨，何事长向别时圆？</span><br><span class="line">人有悲欢离合，月有阴晴圆缺，此事古难全。</span><br><span class="line">但愿人长久，千里共婵娟。</span><br><span class="line">&#123;% endpoem %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><div class='poem'><div class='poem-title'>水调歌头</div><div class='poem-author'>苏轼</div><p>丙辰中秋，欢饮达旦，大醉，作此篇，兼怀子由。<br>明月几时有？把酒问青天。<br>不知天上宫阙，今夕是何年？<br>我欲乘风归去，又恐琼楼玉宇，高处不胜寒。<br>起舞弄清影，何似在人间？</p><p>转朱阁，低绮户，照无眠。<br>不应有恨，何事长向别时圆？<br>人有悲欢离合，月有阴晴圆缺，此事古难全。<br>但愿人长久，千里共婵娟。</p></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-23-阿里图标-icon">2.23 阿里图标 icon</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">参数配置</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% icon [icon-xxxx],[font-size] %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><code>icon-xxxx</code>：表示图标<code>font-class</code>,可以在自己的阿里矢量图标库项目的<code>font-class</code>引用方案内查询并复制。</li><li><code>font-size</code>：表示图标大小，直接填写数字即可，单位为<code>em</code>。图标大小默认值为<code>1em</code>。</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">&#123;% icon icon-rat<span class="emphasis">_zi %&#125;&#123;% icon icon-rat,2 %&#125;</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">&#123;% icon icon-ox_</span>chou,3 %&#125;&#123;% icon icon-ox,4 %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% icon icon-tiger<span class="emphasis">_yin,5 %&#125;&#123;% icon icon-tiger,6 %&#125;</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">&#123;% icon icon-rabbit_</span>mao,1 %&#125;&#123;% icon icon-rabbit,2 %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% icon icon-dragon<span class="emphasis">_chen,3 %&#125;&#123;% icon icon-dragon,4 %&#125;</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">&#123;% icon icon-snake_</span>si,5 %&#125;&#123;% icon icon-snake,6 %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% icon icon-horse<span class="emphasis">_wu %&#125;&#123;% icon icon-horse,2 %&#125;</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">&#123;% icon icon-goat_</span>wei,3 %&#125;&#123;% icon icon-goat,4 %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% icon icon-monkey<span class="emphasis">_shen,5 %&#125;&#123;% icon icon-monkey,6 %&#125;</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">&#123;% icon icon-rooster_</span>you %&#125;&#123;% icon icon-rooster,2 %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% icon icon-dog<span class="emphasis">_xu,3 %&#125;&#123;% icon icon-dog,4 %&#125;</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">&#123;% icon icon-boar_</span>hai,5 %&#125;&#123;% icon icon-boar,6 %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><p><svg class="icon" style="width:1em; height:1em" aria-hidden="true"><use xlink:href="#icon-rat_zi"></use></svg><svg class="icon" style="width:2em; height:2em" aria-hidden="true"><use xlink:href="#icon-rat"></use></svg></p><p><svg class="icon" style="width:3em; height:3em" aria-hidden="true"><use xlink:href="#icon-ox_chou"></use></svg><svg class="icon" style="width:4em; height:4em" aria-hidden="true"><use xlink:href="#icon-ox"></use></svg></p><p><svg class="icon" style="width:5em; height:5em" aria-hidden="true"><use xlink:href="#icon-tiger_yin"></use></svg><svg class="icon" style="width:6em; height:6em" aria-hidden="true"><use xlink:href="#icon-tiger"></use></svg></p><p><svg class="icon" style="width:1em; height:1em" aria-hidden="true"><use xlink:href="#icon-rabbit_mao"></use></svg><svg class="icon" style="width:2em; height:2em" aria-hidden="true"><use xlink:href="#icon-rabbit"></use></svg></p><p><svg class="icon" style="width:3em; height:3em" aria-hidden="true"><use xlink:href="#icon-dragon_chen"></use></svg><svg class="icon" style="width:4em; height:4em" aria-hidden="true"><use xlink:href="#icon-dragon"></use></svg></p><p><svg class="icon" style="width:5em; height:5em" aria-hidden="true"><use xlink:href="#icon-snake_si"></use></svg><svg class="icon" style="width:6em; height:6em" aria-hidden="true"><use xlink:href="#icon-snake"></use></svg></p><p><svg class="icon" style="width:1em; height:1em" aria-hidden="true"><use xlink:href="#icon-horse_wu"></use></svg><svg class="icon" style="width:2em; height:2em" aria-hidden="true"><use xlink:href="#icon-horse"></use></svg></p><p><svg class="icon" style="width:3em; height:3em" aria-hidden="true"><use xlink:href="#icon-goat_wei"></use></svg><svg class="icon" style="width:4em; height:4em" aria-hidden="true"><use xlink:href="#icon-goat"></use></svg></p><p><svg class="icon" style="width:5em; height:5em" aria-hidden="true"><use xlink:href="#icon-monkey_shen"></use></svg><svg class="icon" style="width:6em; height:6em" aria-hidden="true"><use xlink:href="#icon-monkey"></use></svg></p><p><svg class="icon" style="width:1em; height:1em" aria-hidden="true"><use xlink:href="#icon-rooster_you"></use></svg><svg class="icon" style="width:2em; height:2em" aria-hidden="true"><use xlink:href="#icon-rooster"></use></svg></p><p><svg class="icon" style="width:3em; height:3em" aria-hidden="true"><use xlink:href="#icon-dog_xu"></use></svg><svg class="icon" style="width:4em; height:4em" aria-hidden="true"><use xlink:href="#icon-dog"></use></svg></p><p><svg class="icon" style="width:5em; height:5em" aria-hidden="true"><use xlink:href="#icon-boar_hai"></use></svg><svg class="icon" style="width:6em; height:6em" aria-hidden="true"><use xlink:href="#icon-boar"></use></svg></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-24-特效标签wow">2.24 特效标签wow</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-2">渲染演示</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;% wow [animete],[duration],[delay],[offset],[iteration] %&#125;</span><br><span class="line">内容</span><br><span class="line">&#123;% endwow %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><code>animate</code>: 动画样式，效果详见<a href="https://animate.style/">animate.css参考文档</a></li><li><code>duration</code>: 选填项，动画持续时间，单位可以是<code>ms</code>也可以是<code>s</code>。例如<code>3s</code>，<code>700ms</code>。</li><li><code>delay</code>: 选填项，动画开始的延迟时间，单位可以是<code>ms</code>也可以是<code>s</code>。例如<code>3s</code>，<code>700ms</code>。</li><li><code>offset</code>: 选填项，开始动画的距离（相对浏览器底部）</li><li><code>iteration</code>: 选填项，动画重复的次数</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><p>1.flip动画效果。</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;% wow animate<span class="strong">__zoomIn,5s,5s,100,10 %&#125;</span></span><br><span class="line"><span class="strong">&#123;% note blue &#x27;fas fa-bullhorn&#x27; modern%&#125;</span></span><br><span class="line"><span class="strong">`zoomIn`动画效果，持续`5s`，延时`5s`，离底部`100`距离时启动，重复`10`次</span></span><br><span class="line"><span class="strong">&#123;% endnote %&#125;</span></span><br><span class="line"><span class="strong">&#123;% endwow %&#125;</span></span><br></pre></td></tr></table></figure><p>2.zoomIn动画效果，持续5s，延时5s，离底部100距离时启动，重复10次</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;% wow animate<span class="strong">__zoomIn,5s,5s,100,10 %&#125;</span></span><br><span class="line"><span class="strong">&#123;% note blue &#x27;fas fa-bullhorn&#x27; modern%&#125;</span></span><br><span class="line"><span class="strong">`zoomIn`动画效果，持续`5s`，延时`5s`，离底部`100`距离时启动，重复`10`次</span></span><br><span class="line"><span class="strong">&#123;% endnote %&#125;</span></span><br><span class="line"><span class="strong">&#123;% endwow %&#125;</span></span><br></pre></td></tr></table></figure><p>3.slideInRight动画效果，持续5s，延时5s</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;% wow animate<span class="strong">__slideInRight,5s,5s %&#125;</span></span><br><span class="line"><span class="strong">&#123;% note orange &#x27;fas fa-car&#x27; modern%&#125;</span></span><br><span class="line"><span class="strong">`slideInRight`动画效果，持续`5s`，延时`5s`。</span></span><br><span class="line"><span class="strong">&#123;% endnote %&#125;</span></span><br><span class="line"><span class="strong">&#123;% endwow %&#125;</span></span><br></pre></td></tr></table></figure><p>4.heartBeat动画效果，延时5s，重复10次。此处注意不用的参数位置要留空，用逗号间隔。</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;% wow animate<span class="strong">__heartBeat,,5s,,10 %&#125;</span></span><br><span class="line"><span class="strong">&#123;% note red &#x27;fas fa-battery-half&#x27; modern%&#125;</span></span><br><span class="line"><span class="strong">`heartBeat`动画效果，延时`5s`，重复`10`次。</span></span><br><span class="line"><span class="strong">&#123;% endnote %&#125;</span></span><br><span class="line"><span class="strong">&#123;% endwow %&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><p>1.flip动画效果。</p><div class='wow animate__zoomIn' data-wow-duration='5s' data-wow-delay='5s' data-wow-offset='100'  data-wow-iteration='10' ><div class="note blue icon-padding modern"><i class="note-icon fas fa-bullhorn"></i><p><code>zoomIn</code>动画效果，持续<code>5s</code>，延时<code>5s</code>，离底部<code>100</code>距离时启动，重复<code>10</code>次</p></div></div><p>2.zoomIn动画效果，持续5s，延时5s，离底部100距离时启动，重复10次</p><div class='wow animate__zoomIn' data-wow-duration='5s' data-wow-delay='5s' data-wow-offset='100'  data-wow-iteration='10' ><div class="note blue icon-padding modern"><i class="note-icon fas fa-bullhorn"></i><p><code>zoomIn</code>动画效果，持续<code>5s</code>，延时<code>5s</code>，离底部<code>100</code>距离时启动，重复<code>10</code>次</p></div></div><p>3.slideInRight动画效果，持续5s，延时5s</p><div class='wow animate__slideInRight' data-wow-duration='5s' data-wow-delay='5s' data-wow-offset=''  data-wow-iteration='' ><div class="note orange icon-padding modern"><i class="note-icon fas fa-car"></i><p><code>slideInRight</code>动画效果，持续<code>5s</code>，延时<code>5s</code>。</p></div></div><p>4.heartBeat动画效果，延时5s，重复10次。此处注意不用的参数位置要留空，用逗号间隔。</p><div class='wow animate__heartBeat' data-wow-duration='' data-wow-delay='5s' data-wow-offset=''  data-wow-iteration='10' ><div class="note red icon-padding modern"><i class="note-icon fas fa-battery-half"></i><p><code>heartBeat</code>动画效果，延时<code>5s</code>，重复<code>10</code>次。</p></div></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-25-进度条-progress">2.25  进度条 progress</h2><div class="note info flat"><p>进度条标签参考<a href="https://rongbuqiu.com/jdt.html">沂佰孜猫-给HEXO文章添加彩色进度条</a>。<br>源样式提取自<a href="https://zwying0814.gitbook.io/cuteen/">Cuteen</a>主题。</p></div><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">参数配置</button></li><li class="tab"><button type="button" data-href="#分栏-2">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% progress [width] [color] [text] %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><code>width</code>: 0到100的阿拉伯数字</li><li><code>color</code>: 颜色，取值有red,yellow,green,cyan,blue,gray</li><li><code>text</code>:进度条上的文字内容</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;% progress 10 red 进度条样式预览 %&#125;</span><br><span class="line">&#123;% progress 30 yellow 进度条样式预览 %&#125;</span><br><span class="line">&#123;% progress 50 green 进度条样式预览 %&#125;</span><br><span class="line">&#123;% progress 70 cyan 进度条样式预览 %&#125;</span><br><span class="line">&#123;% progress 90 blue 进度条样式预览 %&#125;</span><br><span class="line">&#123;% progress 100 gray 进度条样式预览 %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><div class="progress"><div class="progress-bar-animated progress-bar progress-bar-striped bg-red"  style="width: 10%" aria-valuenow="10" aria-valuemin="0" aria-valuemax="100"><p>进度条样式预览</p></div></div><div class="progress"><div class="progress-bar-animated progress-bar progress-bar-striped bg-yellow"  style="width: 30%" aria-valuenow="30" aria-valuemin="0" aria-valuemax="100"><p>进度条样式预览</p></div></div><div class="progress"><div class="progress-bar-animated progress-bar progress-bar-striped bg-green"  style="width: 50%" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100"><p>进度条样式预览</p></div></div><div class="progress"><div class="progress-bar-animated progress-bar progress-bar-striped bg-cyan"  style="width: 70%" aria-valuenow="70" aria-valuemin="0" aria-valuemax="100"><p>进度条样式预览</p></div></div><div class="progress"><div class="progress-bar-animated progress-bar progress-bar-striped bg-blue"  style="width: 90%" aria-valuenow="90" aria-valuemin="0" aria-valuemax="100"><p>进度条样式预览</p></div></div><div class="progress"><div class="progress-bar-animated progress-bar progress-bar-striped bg-gray"  style="width: 100%" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"><p>进度条样式预览</p></div></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-26-注释-notation">2.26 注释 notation</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">参数配置</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% nota [label] , [text] %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><p><code>label</code>: 注释词汇</p></li><li><p><code>text</code>: 悬停显示的注解内容</p></li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% nota 把鼠标移动到我上面试试 ,可以看到注解内容出现在顶栏 %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><p><span class='nota' data-nota='可以看到注解内容出现在顶栏'>把鼠标移动到我上面试试</span></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-27-气泡注释-bubble">2.27 气泡注释 bubble</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">参数配置</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% bubble [content] , [notation] ,[background-color] %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><code>content</code>: 注释词汇</li><li><code>notation</code>: 悬停显示的注解内容</li><li><code>background-color</code>: 可选，气泡背景色。默认为“#71a4e3”</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">最近我学到了不少新玩意儿（虽然对很多大佬来说这些已经是旧技术了），比如CSS的&#123;% bubble 兄弟相邻选择器,&quot;例如 h1 + p &#123;margin-top:50px;&#125;&quot; %&#125;，&#123;% bubble flex布局,&quot;Flex 是 Flexible Box 的缩写，意为&quot;弹性布局&quot;，用来为盒状模型提供最大的灵活性&quot;,&quot;#ec5830&quot; %&#125;，&#123;% bubble transform变换,&quot;transform 属性向元素应用 2D 或 3D 转换。该属性允许我们对元素进行旋转、缩放、移动或倾斜。&quot;,&quot;#1db675&quot; %&#125;，animation的&#123;% bubble 贝塞尔速度曲线,&quot;贝塞尔曲线(Bézier curve)，又称贝兹曲线或贝济埃曲线，是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线，贝兹曲线由线段与节点组成，节点是可拖动的支点，线段像可伸缩的皮筋&quot;,&quot;#de4489&quot; %&#125;写法，还有今天刚看到的&#123;% bubble clip-path,&quot;clip-path属性使用裁剪方式创建元素的可显示区域。区域内的部分显示，区域外的隐藏。&quot;,&quot;#868fd7&quot; %&#125;属性。这些对我来说很新颖的概念狠狠的冲击着我以前积累起来的设计思路。</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><p>最近我学到了不少新玩意儿（虽然对很多大佬来说这些已经是旧技术了），比如CSS的<span class="bubble-content">兄弟相邻选择器</span><span class="bubble-notation"><span class="bubble-item" style="background-color:#71a4e3;">例如 h1 + p {margin-top:50px;}</span></span>，<span class="bubble-content">flex布局</span><span class="bubble-notation"><span class="bubble-item" style="background-color:#ec5830;">Flex 是 Flexible Box 的缩写，意为弹性布局&quot;，用来为盒状模型提供最大的灵活性&quot;</span></span>，<span class="bubble-content">transform变换</span><span class="bubble-notation"><span class="bubble-item" style="background-color:#1db675;">transform 属性向元素应用 2D 或 3D 转换。该属性允许我们对元素进行旋转、缩放、移动或倾斜。</span></span>，animation的<span class="bubble-content">贝塞尔速度曲线</span><span class="bubble-notation"><span class="bubble-item" style="background-color:#de4489;">贝塞尔曲线(Bézier curve)，又称贝兹曲线或贝济埃曲线，是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线，贝兹曲线由线段与节点组成，节点是可拖动的支点，线段像可伸缩的皮筋</span></span>写法，还有今天刚看到的<span class="bubble-content">clip-path</span><span class="bubble-notation"><span class="bubble-item" style="background-color:#868fd7;">clip-path属性使用裁剪方式创建元素的可显示区域。区域内的部分显示，区域外的隐藏。</span></span>属性。这些对我来说很新颖的概念狠狠的冲击着我以前积累起来的设计思路。</p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-28-引用文献-reference">2.28 引用文献 reference</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">参数配置</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#123;% referto [id] , [literature] %&#125;</span><br><span class="line">&#123;% referfrom [id] , [literature] , [url] %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><p>referto 引用上标</p><ul><li><p><code>id</code>: 上标序号内容，需与referfrom标签的id对应才能实现跳转</p></li><li><p><code>literature</code>: 引用的参考文献名称</p></li></ul></li><li><p>referfrom 引用出处</p><ul><li><p><code>id</code>: 序号内容，需与referto标签的id对应才能实现 跳转</p></li><li><p><code>literature</code>: 引用的参考文献名称</p></li><li><p><code>url</code>: 引用的参考文献链接，可省略</p></li></ul></li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Akilarの糖果屋(akilar.top)是一个私人性质的博客&#123;% referto &#x27;[1]&#x27;,&#x27;Akilarの糖果屋群聊简介&#x27; %&#125;，从各类教程至生活点滴，无话不谈。建群的目的是提供一个闲聊的场所。博客采用Hexo框架&#123;% referto &#x27;[2]&#x27;,&#x27;Hexo中文文档&#x27; %&#125;，Butterfly主题&#123;% referto &#x27;[3]&#x27;,&#x27;Butterfly 安装文档(一) 快速开始&#x27; %&#125;</span><br><span class="line"></span><br><span class="line">本项目参考了Volantis&#123;% referto &#x27;[4]&#x27;,&#x27;hexo-theme-volantis 标签插件&#x27; %&#125;的标签样式。引入<span class="code">`[tag].js`</span>，并针对<span class="code">`butterfly`</span>主题修改了相应的<span class="code">`[tag].styl`</span>。在此鸣谢<span class="code">`Volantis`</span>主题众开发者。</span><br><span class="line">主要参考内容包括各个volantis的内置标签插件文档&#123;% referto &#x27;[5]&#x27;,&#x27;Volantis文档:内置标签插件&#x27; %&#125;</span><br><span class="line">Butterfly主题的各个衍生魔改&#123;% referto &#x27;[6]&#x27;,&#x27;Butterfly 安装文档:标签外挂（Tag Plugins&#x27; %&#125;&#123;% referto &#x27;[7]&#x27;,&#x27;小弋の生活馆全样式预览&#x27; %&#125;&#123;% referto &#x27;[8]&#x27;,&#x27;l-lin-font-awesome-animation&#x27; %&#125;&#123;% referto &#x27;[9]&#x27;,&#x27;小康的butterfly主题使用文档&#x27; %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% referfrom &#x27;[1]&#x27;,&#x27;Akilarの糖果屋群聊简介&#x27;,&#x27;https://jq.qq.com/?<span class="emphasis">_wv=1027&amp;k=pGLB2C0N&#x27; %&#125;</span></span><br><span class="line"><span class="emphasis">&#123;% referfrom &#x27;[2]&#x27;,&#x27;Hexo中文文档&#x27;,&#x27;https://hexo.io/zh-cn/docs/&#x27; %&#125;</span></span><br><span class="line"><span class="emphasis">&#123;% referfrom &#x27;[3]&#x27;,&#x27;Butterfly 安装文档(一) 快速开始&#x27;,&#x27;https://butterfly.js.org/posts/21cfbf15/&#x27; %&#125;</span></span><br><span class="line"><span class="emphasis">&#123;% referfrom &#x27;[4]&#x27;,&#x27;hexo-theme-volantis 标签插件&#x27;,&#x27;https://volantis.js.org/v5/tag-plugins/&#x27; %&#125;</span></span><br><span class="line"><span class="emphasis">&#123;% referfrom &#x27;[5]&#x27;,&#x27;Volantis文档:内置标签插件&#x27;,&#x27;https://volantis.js.org/tag-plugins/&#x27; %&#125;</span></span><br><span class="line"><span class="emphasis">&#123;% referfrom &#x27;[6]&#x27;,&#x27;Butterfly 安装文档:标签外挂（Tag Plugins&#x27;,&#x27;https://butterfly.js.org/posts/4aa8abbe/#%E6%A8%99%E7%B1%A4%E5%A4%96%E6%8E%9B%EF%BC%88Tag-Plugins%EF%BC%89&#x27; %&#125;</span></span><br><span class="line"><span class="emphasis">&#123;% referfrom &#x27;[7]&#x27;,&#x27;小弋の生活馆全样式预览&#x27;,&#x27;https://lovelijunyi.gitee.io/posts/c898.html&#x27; %&#125;</span></span><br><span class="line"><span class="emphasis">&#123;% referfrom &#x27;[8]&#x27;,&#x27;l-lin-font-awesome-animation&#x27;,&#x27;https://github.com/l-lin/font-awesome-animation&#x27; %&#125;</span></span><br><span class="line"><span class="emphasis">&#123;% referfrom &#x27;[9]&#x27;,&#x27;小康的butterfly主题使用文档&#x27;,&#x27;https://www.antmoe.com/posts/3b43914f/&#x27; %&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><p>Akilarの糖果屋(akilar.top)是一个私人性质的博客<span class="hidden-anchor" id="referto_[1]"></span><sup class="reference"><a href="#referfrom_[1]">[1]</a></sup><span class="reference-bubble"><span class="reference-item"><span class="reference-literature">Akilarの糖果屋群聊简介</span><span class="reference-title">参考资料</span></span></span>，从各类教程至生活点滴，无话不谈。建群的目的是提供一个闲聊的场所。博客采用Hexo框架<span class="hidden-anchor" id="referto_[2]"></span><sup class="reference"><a href="#referfrom_[2]">[2]</a></sup><span class="reference-bubble"><span class="reference-item"><span class="reference-literature">Hexo中文文档</span><span class="reference-title">参考资料</span></span></span>，Butterfly主题<span class="hidden-anchor" id="referto_[3]"></span><sup class="reference"><a href="#referfrom_[3]">[3]</a></sup><span class="reference-bubble"><span class="reference-item"><span class="reference-literature">Butterfly 安装文档(一) 快速开始</span><span class="reference-title">参考资料</span></span></span></p><p>本项目参考了Volantis<span class="hidden-anchor" id="referto_[4]"></span><sup class="reference"><a href="#referfrom_[4]">[4]</a></sup><span class="reference-bubble"><span class="reference-item"><span class="reference-literature">hexo-theme-volantis 标签插件</span><span class="reference-title">参考资料</span></span></span>的标签样式。引入<code>[tag].js</code>，并针对<code>butterfly</code>主题修改了相应的<code>[tag].styl</code>。在此鸣谢<code>Volantis</code>主题众开发者。<br>主要参考内容包括各个volantis的内置标签插件文档<span class="hidden-anchor" id="referto_[5]"></span><sup class="reference"><a href="#referfrom_[5]">[5]</a></sup><span class="reference-bubble"><span class="reference-item"><span class="reference-literature">Volantis文档:内置标签插件</span><span class="reference-title">参考资料</span></span></span><br>Butterfly主题的各个衍生魔改<span class="hidden-anchor" id="referto_[6]"></span><sup class="reference"><a href="#referfrom_[6]">[6]</a></sup><span class="reference-bubble"><span class="reference-item"><span class="reference-literature">Butterfly 安装文档:标签外挂（Tag Plugins</span><span class="reference-title">参考资料</span></span></span><span class="hidden-anchor" id="referto_[7]"></span><sup class="reference"><a href="#referfrom_[7]">[7]</a></sup><span class="reference-bubble"><span class="reference-item"><span class="reference-literature">小弋の生活馆全样式预览</span><span class="reference-title">参考资料</span></span></span><span class="hidden-anchor" id="referto_[8]"></span><sup class="reference"><a href="#referfrom_[8]">[8]</a></sup><span class="reference-bubble"><span class="reference-item"><span class="reference-literature">l-lin-font-awesome-animation</span><span class="reference-title">参考资料</span></span></span><span class="hidden-anchor" id="referto_[9]"></span><sup class="reference"><a href="#referfrom_[9]">[9]</a></sup><span class="reference-bubble"><span class="reference-item"><span class="reference-literature">小康的butterfly主题使用文档</span><span class="reference-title">参考资料</span></span></span></p><div class="reference-source"><span class="hidden-anchor" id="referfrom_[1]"></span><a class="reference-anchor" href="#referto_[1]">[1]<div class="reference-anchor-up fa-solid fa-angles-up"></div></a><a class="reference-link" href="https://jq.qq.com/?_wv=1027&k=pGLB2C0N">Akilarの糖果屋群聊简介</a></div><div class="reference-source"><span class="hidden-anchor" id="referfrom_[2]"></span><a class="reference-anchor" href="#referto_[2]">[2]<div class="reference-anchor-up fa-solid fa-angles-up"></div></a><a class="reference-link" href="https://hexo.io/zh-cn/docs/">Hexo中文文档</a></div><div class="reference-source"><span class="hidden-anchor" id="referfrom_[3]"></span><a class="reference-anchor" href="#referto_[3]">[3]<div class="reference-anchor-up fa-solid fa-angles-up"></div></a><a class="reference-link" href="https://butterfly.js.org/posts/21cfbf15/">Butterfly 安装文档(一) 快速开始</a></div><div class="reference-source"><span class="hidden-anchor" id="referfrom_[4]"></span><a class="reference-anchor" href="#referto_[4]">[4]<div class="reference-anchor-up fa-solid fa-angles-up"></div></a><a class="reference-link" href="https://volantis.js.org/v5/tag-plugins/">hexo-theme-volantis 标签插件</a></div><div class="reference-source"><span class="hidden-anchor" id="referfrom_[5]"></span><a class="reference-anchor" href="#referto_[5]">[5]<div class="reference-anchor-up fa-solid fa-angles-up"></div></a><a class="reference-link" href="https://volantis.js.org/tag-plugins/">Volantis文档:内置标签插件</a></div><div class="reference-source"><span class="hidden-anchor" id="referfrom_[6]"></span><a class="reference-anchor" href="#referto_[6]">[6]<div class="reference-anchor-up fa-solid fa-angles-up"></div></a><a class="reference-link" href="https://butterfly.js.org/posts/4aa8abbe/#%E6%A8%99%E7%B1%A4%E5%A4%96%E6%8E%9B%EF%BC%88Tag-Plugins%EF%BC%89">Butterfly 安装文档:标签外挂（Tag Plugins</a></div><div class="reference-source"><span class="hidden-anchor" id="referfrom_[7]"></span><a class="reference-anchor" href="#referto_[7]">[7]<div class="reference-anchor-up fa-solid fa-angles-up"></div></a><a class="reference-link" href="https://lovelijunyi.gitee.io/posts/c898.html">小弋の生活馆全样式预览</a></div><div class="reference-source"><span class="hidden-anchor" id="referfrom_[8]"></span><a class="reference-anchor" href="#referto_[8]">[8]<div class="reference-anchor-up fa-solid fa-angles-up"></div></a><a class="reference-link" href="https://github.com/l-lin/font-awesome-animation">l-lin-font-awesome-animation</a></div><div class="reference-source"><span class="hidden-anchor" id="referfrom_[9]"></span><a class="reference-anchor" href="#referto_[9]">[9]<div class="reference-anchor-up fa-solid fa-angles-up"></div></a><a class="reference-link" href="https://www.antmoe.com/posts/3b43914f/">小康的butterfly主题使用文档</a></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-29-PDF展示">2.29 PDF展示</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">参数配置</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% pdf 文件路径 %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li><code>文件路径</code>: 可以是相对路径或者是在线链接</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># 1.本地文件:在md文件路径下创建一个同名文件夹，其内放pdf文件名为xxx.pdf的文件</span></span><br><span class="line">&#123;% pdf xxx.pdf %&#125;</span><br><span class="line"><span class="section"># 2.在线链接</span></span><br><span class="line">&#123;% pdf https://cdn.jsdelivr.net/gh/Justlovesmile/CDN/pdf/小作文讲义.pdf %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><p>2.在线链接(要放到最外层才能起作用)</p><pre><code>&lt;div class=&quot;row&quot;&gt;&lt;embed src=&quot;https://cdn.jsdelivr.net/gh/Justlovesmile/CDN/pdf/小作文讲义.pdf&quot; width=&quot;100%&quot; height=&quot;550&quot; type=&quot;application/pdf&quot;&gt;&lt;/div&gt;</code></pre><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-30-Hexo-tag-map-插件">2.30 Hexo-tag-map 插件</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">参数配置</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% + 标签值 + 经度 + 纬度 + 文本 + 缩放等级 + 宽 + 高 + 默认图层 + %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><table><thead><tr><th style="text-align:center">地图名</th><th style="text-align:center">标签值 &lt;必填&gt;</th><th style="text-align:center">宽 (默认 100%) / 高 (默认 360px)</th><th style="text-align:center">缩放等级 (默认 14)</th><th style="text-align:center">宽 (默认 100%) / 高 (默认 360px)</th><th style="text-align:center">默认图层 (默认 1)</th></tr></thead><tbody><tr><td style="text-align:center">混合地图</td><td style="text-align:center">map</td><td style="text-align:center">百分数或具体值 (100% 或 360px)</td><td style="text-align:center">取值 3~18</td><td style="text-align:center">百分数或具体值 (100% 或 360px)</td><td style="text-align:center">取值 1~7</td></tr><tr><td style="text-align:center">谷歌地图</td><td style="text-align:center">googleMap</td><td style="text-align:center">百分数或具体值 (100% 或 360px)</td><td style="text-align:center">取值 1~20</td><td style="text-align:center">百分数或具体值 (100% 或 360px)</td><td style="text-align:center">取值 1~3</td></tr><tr><td style="text-align:center">高德地图</td><td style="text-align:center">gaodeMap</td><td style="text-align:center">百分数或具体值 (100% 或 360px)</td><td style="text-align:center">取值 3~18</td><td style="text-align:center">百分数或具体值 (100% 或 360px)</td><td style="text-align:center">取值 1~3</td></tr><tr><td style="text-align:center">百度地图</td><td style="text-align:center">baiduMap</td><td style="text-align:center">百分数或具体值 (100% 或 360px)</td><td style="text-align:center">取值 4~18</td><td style="text-align:center">百分数或具体值 (100% 或 360px)</td><td style="text-align:center">取值 1~2</td></tr><tr><td style="text-align:center">Geoq 地图</td><td style="text-align:center">geoqMap</td><td style="text-align:center">百分数或具体值 (100% 或 360px)</td><td style="text-align:center">取值 1~18</td><td style="text-align:center">百分数或具体值 (100% 或 360px)</td><td style="text-align:center">取值 1~5</td></tr><tr><td style="text-align:center">openstreet 地图</td><td style="text-align:center">openstreetMap</td><td style="text-align:center">百分数或具体值 (100% 或 360px)</td><td style="text-align:center">取值 1~18</td><td style="text-align:center">百分数或具体值 (100% 或 360px)</td><td style="text-align:center">不支持此参数</td></tr></tbody></table><ol><li>参数之间，用英文逗号相隔</li><li>参数必须按上述事例顺序输入，不得为空</li><li>同一个页面，同一组经纬度值，只能插入一个相同标签值的地图 (若有需要，可以将第二个地图上，经度或纬度末尾删除一两个数)</li><li>参数取值必须在上述范围内</li><li>默认图层：即地图叠加层的值，默认常规地图还是卫星地图，可按地图显示顺序取值</li><li>缩放等级，数字越大，地图比例尺越小，显示的越精细</li><li>除标签值外，其他参数选填，但 每个参数的左边的参数必填</li><li>谷歌地图需要外网才能加载查看</li></ol><p>坐标获取：<a href="https://lbs.amap.com/tools/picker">高德地图坐标拾取系统</a> 、<a href="https://api.map.baidu.com/lbsapi/getpoint/index.html">百度地图坐标拾取系统</a></p><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;% map 120.101101,30.239119 %&#125;</span><br><span class="line">&#123;% googleMap 120.101101,30.239119, 这里是西湖灵隐寺，据说求姻缘很灵验哦！ %&#125;</span><br><span class="line">&#123;% geoqMap 120.101101,30.239119, 这里是西湖灵隐寺，据说求姻缘很灵验哦！, 13, 90%, 320px, 3 %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><link rel="stylesheet" href="https://unpkg.com/hexo-tag-map@1.2.1/lib/leaflet@1.7.1.css"><script data-pjax src="https://unpkg.com/hexo-tag-map@1.2.1/lib/leaflet@1.7.1.js"></script><script data-pjax src="https://unpkg.com/hexo-tag-map@1.2.1/lib/leaflet.ChineseTmsProviders@1.0.4.js"></script><div class="map-box" style="margin: 0.8rem 0 1.6rem 0;"><div id="map-120.101101-30.239119" style="max-width:100%; height:360px;display: block;margin:0 auto;z-index:1;border-radius: 5px;"></div></div><script type="text/javascript">var normalm=L.tileLayer.chinaProvider('GaoDe.Normal.Map',{maxZoom:20,minZoom:1,attribution:'高德地图'});var imgm=L.tileLayer.chinaProvider('GaoDe.Satellite.Map',{maxZoom:20,minZoom:1,attribution:'高德地图'});var imga=L.tileLayer.chinaProvider('GaoDe.Satellite.Annotion',{maxZoom:20,minZoom:1,attribution:'高德地图'});var normalMap=L.tileLayer.chinaProvider('Google.Normal.Map',{maxZoom:20,minZoom:1,attribution:'Google Maps'}),satelliteMap=L.tileLayer.chinaProvider('Google.Satellite.Map',{maxZoom:21,minZoom:1,attribution:'Google Maps'});routeMap=L.tileLayer.chinaProvider('Google.Satellite.Annotion',{maxZoom:21,minZoom:1});var normalMap=L.tileLayer.chinaProvider('Google.Normal.Map',{maxZoom:21,minZoom:1,attribution:'Google Maps'}),satelliteMap=L.tileLayer.chinaProvider('Google.Satellite.Map',{maxZoom:21,minZoom:1,attribution:'Google Maps'}),routeMap=L.tileLayer.chinaProvider('Google.Satellite.Annotion',{maxZoom:21,minZoom:1,attribution:'Google Maps'});var normalm1=L.tileLayer.chinaProvider('Geoq.Normal.Map',{maxZoom:21,minZoom:1,attribution:'GeoQ'});var normal=L.layerGroup([normalm]),image=L.layerGroup([imgm,imga]);var baseLayers={"高德地图":normal,"智图地图":normalm1,"谷歌地图":normalMap,"高德卫星地图":imgm,"谷歌卫星地图":satelliteMap,"高德卫星标注":image,"谷歌卫星标注":routeMap};var mymap=L.map('map-120.101101-30.239119',{center:[30.239119,120.101101],zoom:14,layers:[normal],zoomControl:false});L.control.layers(baseLayers,null).addTo(mymap);L.control.zoom({zoomInTitle:'放大',zoomOutTitle:'缩小'}).addTo(mymap);</script><br><link rel="stylesheet" href="https://unpkg.com/hexo-tag-map@1.2.1/lib/leaflet@1.7.1.css"><script data-pjax src="https://unpkg.com/hexo-tag-map@1.2.1/lib/leaflet@1.7.1.js"></script><script data-pjax src="https://unpkg.com/hexo-tag-map@1.2.1/lib/leaflet.ChineseTmsProviders@1.0.4.js"></script><div id="googleMap-120.101101-30.239119" style="max-width:100%; height:360px;display: block;margin:0 auto;z-index:1;border-radius: 5px;"></div><script type="text/javascript">var normalMap=L.tileLayer.chinaProvider('Google.Normal.Map',{maxZoom:22,minZoom:1,attribution:'Google Maps'}),satelliteMap=L.tileLayer.chinaProvider('Google.Satellite.Map',{maxZoom:22,minZoom:1,attribution:'Google Maps'}),routeMap=L.tileLayer.chinaProvider('Google.Satellite.Annotion',{maxZoom:22,minZoom:1,attribution:'Google Maps'});var baseLayers={"谷歌地图":normalMap,"谷歌卫星图":satelliteMap,"谷歌卫星标注": routeMap};var overlayLayers={};var mymap=L.map("googleMap-120.101101-30.239119",{center:[30.239119,120.101101],zoom:14,layers:[normalMap],zoomControl:false});L.control.layers(baseLayers,null).addTo(mymap);L.control.zoom({zoomInTitle:'放大',zoomOutTitle:'缩小'}).addTo(mymap);var marker = L.marker(['30.239119','120.101101']).addTo(mymap);marker.bindPopup("这里是西湖灵隐寺，据说求姻缘很灵验哦！").openPopup();</script><br><link rel="stylesheet" href="https://unpkg.com/hexo-tag-map@1.2.1/lib/leaflet@1.7.1.css"><script data-pjax src="https://unpkg.com/hexo-tag-map@1.2.1/lib/leaflet@1.7.1.js"></script><script data-pjax src="https://unpkg.com/hexo-tag-map@1.2.1/lib/leaflet.ChineseTmsProviders@1.0.4.js"></script><div id="geoqMap-120.101101-30.239119" style="max-width:90%; height:320px;display: block;margin:0 auto;z-index:1;border-radius: 5px;"></div><script type="text/javascript">var normalm1=L.tileLayer.chinaProvider('Geoq.Normal.Map',{maxZoom:20,minZoom:1,attribution:'GeoQ'});var normalm2=L.tileLayer.chinaProvider('Geoq.Normal.PurplishBlue',{maxZoom:20,minZoom:1,attribution:'GeoQ'});var normalm3=L.tileLayer.chinaProvider('Geoq.Normal.Gray',{maxZoom:20,minZoom:1,attribution:'GeoQ'});var normalm4=L.tileLayer.chinaProvider('Geoq.Normal.Warm',{maxZoom:20,minZoom:1,attribution:'GeoQ'});var normalm5=L.tileLayer.chinaProvider('Geoq.Theme.Hydro',{maxZoom:20,minZoom:1,attribution:'GeoQ'});var normal=L.layerGroup([normalm1,normalm2,normalm3,normalm4,normalm5]);var baseLayers={"智图地图":normalm1,"午夜蓝":normalm2,"灰色":normalm3,"暖色":normalm4,"水系":normalm5};var mymap=L.map("geoqMap-120.101101-30.239119",{center:[30.239119,120.101101],zoom:13,layers:[normalm3],zoomControl:false});L.control.layers(baseLayers,null).addTo(mymap);L.control.zoom({zoomInTitle:'放大',zoomOutTitle:'缩小'}).addTo(mymap);var marker = L.marker(['30.239119','120.101101']).addTo(mymap);marker.bindPopup("这里是西湖灵隐寺，据说求姻缘很灵验哦！").openPopup();</script><br><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="2-31-隐藏块">2.31 隐藏块</h2><div class="tabs" id="分栏"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分栏-1">标签语法</button></li><li class="tab"><button type="button" data-href="#分栏-2">参数配置</button></li><li class="tab"><button type="button" data-href="#分栏-3">示例源码</button></li><li class="tab"><button type="button" data-href="#分栏-4">渲染演示</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分栏-1"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;% hideBlock display,bg,color %&#125;</span><br><span class="line">content</span><br><span class="line">&#123;% endhideBlock %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-2"><ol><li>content：要隐藏的内容</li><li>display：展示前按钮显示的文字（可选）</li><li>bg：按钮的背景颜色（可选）</li><li>color：按钮显示的文字的颜色（可选）</li></ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-3"><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;% hideBlock 点我预览, blue %&#125;</span><br><span class="line">这里有张图片：</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;https://s1.vika.cn/space/2022/10/30/b35fce448bc9404a8d65c3ce1e6e46eb&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;image (1)&quot;</span> <span class="attr">style</span>=<span class="string">&quot;zoom:67%;&quot;</span> /&gt;</span></span></span><br><span class="line">&#123;% endhideBlock %&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分栏-4"><div class="hide-block"><button type="button" class="hide-button" style="background-color:  blue;">点我预览    </button><div class="hide-content"><p>这里有张图片：<br><img src="https://s1.vika.cn/space/2022/10/30/b35fce448bc9404a8d65c3ce1e6e46eb" alt="image (1)" style="zoom:67%;" /></p></div></div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div>]]></content>
    
    
    <summary type="html">🥧本文汇总Markdown格式以及外挂标签在网页端的渲染效果，可作为文档进行查询</summary>
    
    
    
    <category term="书山指路" scheme="https://blog.adoreorg.cn/categories/%E4%B9%A6%E5%B1%B1%E6%8C%87%E8%B7%AF/"/>
    
    
    <category term="Markdown" scheme="https://blog.adoreorg.cn/tags/Markdown/"/>
    
  </entry>
  
  <entry>
    <title>Git使用教程：Hexo博客必备技能</title>
    <link href="https://blog.adoreorg.cn/posts/713beab8.html"/>
    <id>https://blog.adoreorg.cn/posts/713beab8.html</id>
    <published>2025-08-01T06:30:00.000Z</published>
    <updated>2025-09-20T05:21:52.863Z</updated>
    
    <content type="html"><![CDATA[<h2 id="🎯-为什么博客需要Git？">🎯 为什么博客需要Git？</h2><p>在Hexo博客中使用Git，不仅能实现版本控制，还能配合GitHub Actions实现自动化部署。本教程将带你从零开始掌握博客必备的Git技能。</p><h2 id="📦-基础概念">📦 基础概念</h2><h3 id="Git工作区域">Git工作区域</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">工作区(Working Directory) → 暂存区(Staging Area) → 本地仓库(Repository) → 远程仓库(Remote)</span><br></pre></td></tr></table></figure><h3 id="博客文件状态">博客文件状态</h3><ul><li><strong>已修改(modified)</strong>：修改了文件，还未提交</li><li><strong>已暂存(staged)</strong>：标记了要提交的文件</li><li><strong>已提交(committed)</strong>：文件已安全保存在本地仓库</li></ul><h2 id="🚀-博客日常操作">🚀 博客日常操作</h2><h3 id="1-新建文章并提交">1. 新建文章并提交</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建新文章</span></span><br><span class="line">hexo new post <span class="string">&quot;我的新文章&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 编辑完成后</span></span><br><span class="line">git add <span class="built_in">source</span>/_posts/我的新文章.md</span><br><span class="line">git commit -m <span class="string">&quot;发布新文章：我的新文章&quot;</span></span><br><span class="line">git push origin main</span><br></pre></td></tr></table></figure><h3 id="2-修改文章内容">2. 修改文章内容</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 修改文章内容后</span></span><br><span class="line">vim <span class="built_in">source</span>/_posts/我的新文章.md  <span class="comment"># 编辑文章</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看修改了什么</span></span><br><span class="line">git diff <span class="built_in">source</span>/_posts/我的新文章.md</span><br><span class="line"></span><br><span class="line"><span class="comment"># 提交修改</span></span><br><span class="line">git add <span class="built_in">source</span>/_posts/我的新文章.md</span><br><span class="line">git commit -m <span class="string">&quot;修正文章格式和错别字&quot;</span></span><br><span class="line">git push origin main</span><br></pre></td></tr></table></figure><h3 id="3-修改网站配置">3. 修改网站配置</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 修改主题配置后</span></span><br><span class="line">vim _config.butterfly.yml</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看所有修改</span></span><br><span class="line">git status</span><br><span class="line"></span><br><span class="line"><span class="comment"># 选择性提交</span></span><br><span class="line">git add _config.butterfly.yml</span><br><span class="line">git commit -m <span class="string">&quot;更新主题配色方案&quot;</span></span><br><span class="line">git push origin main</span><br></pre></td></tr></table></figure><h2 id="📝-实用命令清单">📝 实用命令清单</h2><h3 id="查看状态和历史">查看状态和历史</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看当前状态</span></span><br><span class="line">git status</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看提交历史</span></span><br><span class="line">git <span class="built_in">log</span> --oneline --graph --decorate</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看文件修改历史</span></span><br><span class="line">git <span class="built_in">log</span> --oneline <span class="built_in">source</span>/_posts/文章名.md</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看具体修改内容</span></span><br><span class="line">git diff 文件名</span><br></pre></td></tr></table></figure><h3 id="分支管理（高级用法）">分支管理（高级用法）</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建新分支写文章</span></span><br><span class="line">git checkout -b draft-new-post</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在新分支上写文章</span></span><br><span class="line">hexo new post <span class="string">&quot;草稿文章&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 完成后再合并到main分支</span></span><br><span class="line">git checkout main</span><br><span class="line">git merge draft-new-post</span><br><span class="line">git push origin main</span><br></pre></td></tr></table></figure><h2 id="🔄-回滚操作详解">🔄 回滚操作详解</h2><h3 id="场景1：文章内容写错了">场景1：文章内容写错了</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看提交历史</span></span><br><span class="line">git <span class="built_in">log</span> --oneline</span><br><span class="line"></span><br><span class="line"><span class="comment"># 回滚到指定版本（保留历史）</span></span><br><span class="line">git revert abc123</span><br><span class="line">git push origin main</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或者彻底回滚（删除历史）</span></span><br><span class="line">git reset --hard abc123</span><br><span class="line">git push --force origin main</span><br></pre></td></tr></table></figure><h3 id="场景2：只回滚单个文件">场景2：只回滚单个文件</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 只回滚文章内容，不影响其他配置</span></span><br><span class="line">git checkout HEAD~1 -- <span class="built_in">source</span>/_posts/文章名.md</span><br><span class="line">git commit -m <span class="string">&quot;回滚文章内容到之前版本&quot;</span></span><br><span class="line">git push origin main</span><br></pre></td></tr></table></figure><h3 id="场景3：使用GitHub网页回滚">场景3：使用GitHub网页回滚</h3><ol><li>访问GitHub仓库 → Commits</li><li>找到要回滚的提交 → 点击&quot;Revert&quot;</li><li>自动创建回滚提交，无需命令行</li></ol><h2 id="⚡-高效工作流">⚡ 高效工作流</h2><h3 id="每日写作流程">每日写作流程</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 开始写作前拉取最新代码</span></span><br><span class="line">git pull origin main</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 创建新文章</span></span><br><span class="line">hexo new post <span class="string">&quot;<span class="subst">$(date +%Y-%m-%d)</span>-今日文章&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 编辑文章（用你喜欢的编辑器）</span></span><br><span class="line">code <span class="built_in">source</span>/_posts/$(<span class="built_in">date</span> +%Y-%m-%d)-今日文章.md</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 本地预览（可选）</span></span><br><span class="line">hexo server</span><br><span class="line"></span><br><span class="line"><span class="comment"># 5. 提交并推送</span></span><br><span class="line">git add .</span><br><span class="line">git commit -m <span class="string">&quot;发布新文章：<span class="subst">$(date +%Y-%m-%d)</span>-今日文章&quot;</span></span><br><span class="line">git push origin main</span><br></pre></td></tr></table></figure><h3 id="批量操作技巧">批量操作技巧</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 一次性提交多个修改</span></span><br><span class="line">git add .</span><br><span class="line">git commit -m <span class="string">&quot;更新：新增Redis教程，修改主题配置，添加友情链接&quot;</span></span><br><span class="line">git push origin main</span><br><span class="line"></span><br><span class="line"><span class="comment"># 忽略某些文件不提交</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;draft-*.md&quot;</span> &gt;&gt; .gitignore</span><br><span class="line">git add .gitignore</span><br><span class="line">git commit -m <span class="string">&quot;忽略草稿文件&quot;</span></span><br></pre></td></tr></table></figure><h2 id="🎨-高级技巧">🎨 高级技巧</h2><h3 id="提交信息规范">提交信息规范</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 好的提交信息示例</span></span><br><span class="line">git commit -m <span class="string">&quot;文章：添加Redis集群配置教程&quot;</span></span><br><span class="line">git commit -m <span class="string">&quot;修复：修正MySQL教程中的语法错误&quot;</span></span><br><span class="line">git commit -m <span class="string">&quot;主题：更新导航栏样式&quot;</span></span><br></pre></td></tr></table></figure><h3 id="备份策略">备份策略</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建备份分支</span></span><br><span class="line">git branch backup-$(<span class="built_in">date</span> +%Y%m%d)</span><br><span class="line">git push origin backup-$(<span class="built_in">date</span> +%Y%m%d)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定期清理旧备份</span></span><br><span class="line">git branch -d backup-20241101</span><br><span class="line">git push origin --delete backup-20241101</span><br></pre></td></tr></table></figure><h2 id="📱-移动设备写作">📱 移动设备写作</h2><h3 id="手机-平板操作">手机/平板操作</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用GitHub移动端或第三方App</span></span><br><span class="line"><span class="comment"># 工作流程：</span></span><br><span class="line"><span class="comment"># 1. 手机编辑Markdown文件</span></span><br><span class="line"><span class="comment"># 2. 通过GitHub App提交</span></span><br><span class="line"><span class="comment"># 3. 自动部署完成</span></span><br></pre></td></tr></table></figure><h2 id="🔧-常见问题解决">🔧 常见问题解决</h2><h3 id="推送被拒绝">推送被拒绝</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 先拉取最新更改</span></span><br><span class="line">git pull origin main</span><br><span class="line"><span class="comment"># 解决冲突后再次推送</span></span><br><span class="line">git push origin main</span><br></pre></td></tr></table></figure><h3 id="误提交敏感信息">误提交敏感信息</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 如果提交了密码等敏感信息</span></span><br><span class="line">git reset --hard HEAD~1</span><br><span class="line">git push --force origin main</span><br><span class="line"><span class="comment"># 然后修改文件重新提交</span></span><br></pre></td></tr></table></figure><h2 id="🎯-最佳实践总结">🎯 最佳实践总结</h2><ol><li><strong>小步快跑</strong>：每次只提交一个逻辑修改</li><li><strong>清晰描述</strong>：提交信息要说明修改了什么</li><li><strong>及时备份</strong>：重要修改后立即推送</li><li><strong>分支管理</strong>：复杂修改使用分支</li><li><strong>定期清理</strong>：删除无用分支和旧备份</li></ol><h2 id="📚-学习资源">📚 学习资源</h2><ul><li><a href="https://git-scm.com/doc">Git官方文档</a></li><li><a href="https://lab.github.com">GitHub学习实验室</a></li><li><a href="https://git-scm.com/book/zh/v2">Pro Git中文版</a></li></ul><hr><blockquote><p>💡 <strong>小贴士</strong>：刚开始记不住命令没关系，把常用命令贴在显示器旁边，用几次就熟练了！</p></blockquote><blockquote><p>🚀 <strong>效率提升</strong>：配合GitHub Actions，你的<code>git push origin main</code>就是一键发布的魔法棒！</p></blockquote>]]></content>
    
    
    <summary type="html">详细讲解Git在Hexo博客中的实际应用，从基础命令到高级技巧，配合GitHub Actions实现自动化部署。</summary>
    
    
    
    <category term="笔耕问道" scheme="https://blog.adoreorg.cn/categories/%E7%AC%94%E8%80%95%E9%97%AE%E9%81%93/"/>
    
    
    <category term="Git" scheme="https://blog.adoreorg.cn/tags/Git/"/>
    
    <category term="Hexo" scheme="https://blog.adoreorg.cn/tags/Hexo/"/>
    
    <category term="教程" scheme="https://blog.adoreorg.cn/tags/%E6%95%99%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>派洞察 - 聊天助手设计模块</title>
    <link href="https://blog.adoreorg.cn/posts/3f8a2b91.html"/>
    <id>https://blog.adoreorg.cn/posts/3f8a2b91.html</id>
    <published>2025-07-24T16:00:00.000Z</published>
    <updated>2025-11-21T14:09:18.019Z</updated>
    
    <content type="html"><![CDATA[<h1>🎯 派洞察——科研文献智能 RAG 知识库系统</h1><h2 id="💬-聊天助手设计模块">💬 聊天助手设计模块</h2><blockquote><p><strong>模块定位</strong>：聊天助手设计模块是系统的智能交互核心，负责处理用户与AI的实时对话和消息处理功能。</p></blockquote><h3 id="🎯-核心目标">🎯 核心目标</h3><ul><li>✅ <strong>实时通信</strong>：提供基于WebSocket的实时双向通信能力</li><li>✅ <strong>智能对话</strong>：集成AI模型实现智能问答和知识检索</li><li>✅ <strong>消息处理</strong>：高效处理用户消息并生成AI响应</li><li>✅ <strong>状态管理</strong>：维护对话状态和会话管理</li><li>✅ <strong>安全控制</strong>：确保通信安全和用户身份验证</li></ul><h1>📊 一、功能需求</h1><h2 id="💭-实时聊天">💭 实时聊天</h2><blockquote><p>📝 <strong>功能描述</strong>：基于WebSocket协议实现用户与AI助手的实时双向通信，支持文本消息的发送和接收。</p><p>⚡ <strong>实时性</strong>：消息传输延迟低，支持高并发用户同时在线聊天。</p></blockquote><h2 id="🔗-WebSocket连接管理">🔗 WebSocket连接管理</h2><blockquote><p>🔌 <strong>连接建立</strong>：支持客户端与服务器建立WebSocket连接，进行全双工通信。</p><p>🔄 <strong>连接维护</strong>：自动处理连接状态，支持心跳检测和连接重连。</p><p>❌ <strong>连接关闭</strong>：优雅处理连接关闭，释放相关资源。</p></blockquote><h2 id="🤖-AI消息处理">🤖 AI消息处理</h2><blockquote><p>🎯 <strong>消息接收</strong>：接收用户发送的文本消息，进行预处理和验证。</p><p>🧠 <strong>智能处理</strong>：调用AI服务处理用户消息，生成智能回复内容。</p><p>📤 <strong>响应发送</strong>：将AI生成的回复内容通过WebSocket发送给用户。</p></blockquote><h2 id="🎛️-聊天控制">🎛️ 聊天控制</h2><blockquote><p>🛑 <strong>停止指令</strong>：提供WebSocket停止指令Token，支持用户主动停止AI响应生成。</p><p>🔒 <strong>权限控制</strong>：确保只有授权用户才能使用聊天功能。</p><p>📊 <strong>性能监控</strong>：实时监控聊天处理性能，记录关键指标。</p></blockquote><h1>🛠️ 二、技术方案</h1><h2 id="🌐-通信协议">🌐 通信协议</h2><blockquote><p>🔗 <strong>WebSocket</strong>：使用WebSocket协议实现全双工通信，支持实时消息传输。</p><p>📡 <strong>Spring WebSocket</strong>：基于Spring框架的WebSocket支持，提供稳定的连接管理。</p><p>💓 <strong>心跳机制</strong>：实现心跳检测，确保连接活跃状态。</p></blockquote><h2 id="🤖-AI集成">🤖 AI集成</h2><blockquote><p>🧠 <strong>ChatHandler</strong>：自定义聊天处理器，负责调用AI服务处理用户消息。</p><p>🔄 <strong>异步处理</strong>：采用异步处理方式，避免阻塞WebSocket连接。</p><p>🎯 <strong>上下文管理</strong>：维护对话上下文，提供连贯的对话体验。</p></blockquote><h2 id="🛡️-安全与监控">🛡️ 安全与监控</h2><blockquote><p>🔐 <strong>身份验证</strong>：通过会话ID识别用户身份，确保通信安全。</p><p>📝 <strong>日志记录</strong>：详细记录聊天日志，包括用户消息、AI响应和系统事件。</p><p>⚠️ <strong>错误处理</strong>：完善的异常处理机制，确保系统稳定性。</p><p>📈 <strong>性能监控</strong>：集成性能监控工具，实时跟踪处理耗时和资源使用情况。</p></blockquote><h2 id="🗄️-数据存储">🗄️ 数据存储</h2><blockquote><p>💾 <strong>消息持久化</strong>：支持聊天记录的持久化存储，便于后续分析。</p><p>🔄 <strong>缓存机制</strong>：使用缓存技术提高响应速度和系统性能。</p><p>📊 <strong>数据分析</strong>：收集聊天数据用于AI模型优化和用户体验改进。</p></blockquote><h1>📝 三、关键流程</h1><h2 id="💬-1-WebSocket聊天消息处理">💬 1. WebSocket聊天消息处理</h2><h3 id="🔄-流程步骤">🔄 流程步骤</h3><ol><li>🔗 <strong>建立连接</strong>：客户端与服务器建立WebSocket连接</li><li>📨 <strong>接收消息</strong>：服务器接收用户发送的文本消息</li><li>🆔 <strong>身份识别</strong>：使用会话ID作为用户标识</li><li>⏱️ <strong>性能监控</strong>：启动性能监控器记录处理耗时</li><li>📝 <strong>日志记录</strong>：记录用户消息信息和业务日志</li><li>🤖 <strong>AI处理</strong>：调用ChatHandler处理用户消息</li><li>✅ <strong>成功监控</strong>：记录操作成功日志和性能指标</li><li>📤 <strong>响应返回</strong>：将AI响应发送给客户端</li></ol><p><strong>异常处理</strong>：</p><ul><li>记录业务错误日志</li><li>更新性能监控状态</li><li>抛出异常供上层处理</li></ul><h2 id="🔑-2-获取WebSocket停止指令Token">🔑 2. 获取WebSocket停止指令Token</h2><h3 id="🔄-流程步骤-2">🔄 流程步骤</h3><ol><li>📤 <strong>接收请求</strong>：客户端发送获取Token请求</li><li>🎯 <strong>Token生成</strong>：调用ChatWebSocketHandler生成内部命令Token</li><li>✅ <strong>有效性检查</strong>：验证生成的Token是否有效</li><li>📤 <strong>返回结果</strong>：返回Token或错误信息</li></ol><p><strong>错误处理</strong>：</p><ul><li>Token生成失败返回500错误</li><li>记录业务错误日志</li><li>返回详细的错误信息</li></ul><p><strong>接口设计</strong></p><div class="tabs" id="get/websocket-token"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#get/websocket-token-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#get/websocket-token-2">成功响应</button></li><li class="tab"><button type="button" data-href="#get/websocket-token-3">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="get/websocket-token-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET /api/v1/chat/websocket-token</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/websocket-token-2"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;获取WebSocket停止指令Token成功&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;cmdToken&quot;</span><span class="punctuation">:</span> <span class="string">&quot;STOP_TOKEN_STRING&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/websocket-token-3"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">500</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Token生成失败&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h1>🗃️ 四、库表设计</h1><h2 id="💬-1-聊天记录表">💬 1. 聊天记录表</h2><div class="tabs" id="chat-record-table"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#chat-record-table-1">字段设计</button></li><li class="tab"><button type="button" data-href="#chat-record-table-2">建表语句</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="chat-record-table-1"><p><strong>字段设计</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">| 🔑 字段 | 📋 类型 | 📝 描述 |</span><br><span class="line">| --- | --- | --- |</span><br><span class="line">| <span class="code">`id`</span> | <span class="code">`BIGINT`</span> | 🆔 主键，聊天记录ID |</span><br><span class="line">| <span class="code">`user_id`</span> | <span class="code">`VARCHAR(64)`</span> | 👤 用户会话ID |</span><br><span class="line">| <span class="code">`message`</span> | <span class="code">`TEXT`</span> | 💬 用户消息内容 |</span><br><span class="line">| <span class="code">`response`</span> | <span class="code">`TEXT`</span> | 🤖 AI响应内容 |</span><br><span class="line">| <span class="code">`message_type`</span> | <span class="code">`ENUM(&#x27;USER&#x27;,&#x27;AI&#x27;)`</span> | 📨 消息类型 |</span><br><span class="line">| <span class="code">`status`</span> | <span class="code">`ENUM(&#x27;PENDING&#x27;,&#x27;SUCCESS&#x27;,&#x27;FAILED&#x27;)`</span> | 📊 处理状态 |</span><br><span class="line">| <span class="code">`processing_time`</span> | <span class="code">`INT`</span> | ⏱️ 处理耗时（毫秒） |</span><br><span class="line">| <span class="code">`created_at`</span> | <span class="code">`TIMESTAMP`</span> | 📅 创建时间 |</span><br><span class="line">| <span class="code">`updated_at`</span> | <span class="code">`TIMESTAMP`</span> | 🔄 更新时间 |</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="chat-record-table-2"><p><strong>建表语句</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> chat_records (</span><br><span class="line">    id <span class="type">BIGINT</span> AUTO_INCREMENT <span class="keyword">PRIMARY KEY</span> COMMENT <span class="string">&#x27;聊天记录唯一标识&#x27;</span>,</span><br><span class="line">    user_id <span class="type">VARCHAR</span>(<span class="number">64</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;用户会话ID&#x27;</span>,</span><br><span class="line">    message TEXT COMMENT <span class="string">&#x27;用户消息内容&#x27;</span>,</span><br><span class="line">    response TEXT COMMENT <span class="string">&#x27;AI响应内容&#x27;</span>,</span><br><span class="line">    message_type ENUM(<span class="string">&#x27;USER&#x27;</span>,<span class="string">&#x27;AI&#x27;</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;消息类型&#x27;</span>,</span><br><span class="line">    status ENUM(<span class="string">&#x27;PENDING&#x27;</span>,<span class="string">&#x27;SUCCESS&#x27;</span>,<span class="string">&#x27;FAILED&#x27;</span>) <span class="keyword">DEFAULT</span> <span class="string">&#x27;PENDING&#x27;</span> COMMENT <span class="string">&#x27;处理状态&#x27;</span>,</span><br><span class="line">    processing_time <span class="type">INT</span> <span class="keyword">DEFAULT</span> <span class="number">0</span> COMMENT <span class="string">&#x27;处理耗时（毫秒）&#x27;</span>,</span><br><span class="line">    created_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;创建时间&#x27;</span>,</span><br><span class="line">    updated_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> <span class="keyword">ON</span> <span class="keyword">UPDATE</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;更新时间&#x27;</span>,</span><br><span class="line">    INDEX idx_user_id (user_id) COMMENT <span class="string">&#x27;用户ID索引&#x27;</span>,</span><br><span class="line">    INDEX idx_created_at (created_at) COMMENT <span class="string">&#x27;创建时间索引&#x27;</span></span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 COMMENT<span class="operator">=</span><span class="string">&#x27;聊天记录表&#x27;</span>;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="🔗-2-WebSocket会话表">🔗 2. WebSocket会话表</h2><div class="tabs" id="websocket-session-table"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#websocket-session-table-1">字段设计</button></li><li class="tab"><button type="button" data-href="#websocket-session-table-2">建表语句</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="websocket-session-table-1"><p><strong>字段设计</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">| 🔑 字段 | 📋 类型 | 📝 描述 |</span><br><span class="line">| --- | --- | --- |</span><br><span class="line">| <span class="code">`session_id`</span> | <span class="code">`VARCHAR(64)`</span> | 🆔 主键，会话ID |</span><br><span class="line">| <span class="code">`user_id`</span> | <span class="code">`VARCHAR(64)`</span> | 👤 用户ID |</span><br><span class="line">| <span class="code">`connection_status`</span> | <span class="code">`ENUM(&#x27;CONNECTED&#x27;,&#x27;DISCONNECTED&#x27;)`</span> | 🔗 连接状态 |</span><br><span class="line">| <span class="code">`last_heartbeat`</span> | <span class="code">`TIMESTAMP`</span> | 💓 最后心跳时间 |</span><br><span class="line">| <span class="code">`connection_time`</span> | <span class="code">`TIMESTAMP`</span> | ⏱️ 连接建立时间 |</span><br><span class="line">| <span class="code">`disconnection_time`</span> | <span class="code">`TIMESTAMP`</span> | 🚪 连接断开时间 |</span><br><span class="line">| <span class="code">`ip_address`</span> | <span class="code">`VARCHAR(45)`</span> | 🌐 客户端IP地址 |</span><br><span class="line">| <span class="code">`created_at`</span> | <span class="code">`TIMESTAMP`</span> | 📅 创建时间 |</span><br><span class="line">| <span class="code">`updated_at`</span> | <span class="code">`TIMESTAMP`</span> | 🔄 更新时间 |</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="websocket-session-table-2"><p><strong>建表语句</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> websocket_sessions (</span><br><span class="line">    session_id <span class="type">VARCHAR</span>(<span class="number">64</span>) <span class="keyword">PRIMARY KEY</span> COMMENT <span class="string">&#x27;会话唯一标识&#x27;</span>,</span><br><span class="line">    user_id <span class="type">VARCHAR</span>(<span class="number">64</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;用户ID&#x27;</span>,</span><br><span class="line">    connection_status ENUM(<span class="string">&#x27;CONNECTED&#x27;</span>,<span class="string">&#x27;DISCONNECTED&#x27;</span>) <span class="keyword">DEFAULT</span> <span class="string">&#x27;CONNECTED&#x27;</span> COMMENT <span class="string">&#x27;连接状态&#x27;</span>,</span><br><span class="line">    last_heartbeat <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;最后心跳时间&#x27;</span>,</span><br><span class="line">    connection_time <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;连接建立时间&#x27;</span>,</span><br><span class="line">    disconnection_time <span class="type">TIMESTAMP</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;连接断开时间&#x27;</span>,</span><br><span class="line">    ip_address <span class="type">VARCHAR</span>(<span class="number">45</span>) COMMENT <span class="string">&#x27;客户端IP地址&#x27;</span>,</span><br><span class="line">    created_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;创建时间&#x27;</span>,</span><br><span class="line">    updated_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> <span class="keyword">ON</span> <span class="keyword">UPDATE</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;更新时间&#x27;</span>,</span><br><span class="line">    INDEX idx_user_id (user_id) COMMENT <span class="string">&#x27;用户ID索引&#x27;</span>,</span><br><span class="line">    INDEX idx_connection_status (connection_status) COMMENT <span class="string">&#x27;连接状态索引&#x27;</span>,</span><br><span class="line">    INDEX idx_last_heartbeat (last_heartbeat) COMMENT <span class="string">&#x27;心跳时间索引&#x27;</span></span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 COMMENT<span class="operator">=</span><span class="string">&#x27;WebSocket会话表&#x27;</span>;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div>]]></content>
    
    
    <summary type="html">科研文献智能 RAG 知识库系统聊天助手设计模块详细设计文档</summary>
    
    
    
    <category term="学海拾贝" scheme="https://blog.adoreorg.cn/categories/%E5%AD%A6%E6%B5%B7%E6%8B%BE%E8%B4%9D/"/>
    
    
    <category term="聊天助手" scheme="https://blog.adoreorg.cn/tags/%E8%81%8A%E5%A4%A9%E5%8A%A9%E6%89%8B/"/>
    
    <category term="WebSocket" scheme="https://blog.adoreorg.cn/tags/WebSocket/"/>
    
    <category term="实时通信" scheme="https://blog.adoreorg.cn/tags/%E5%AE%9E%E6%97%B6%E9%80%9A%E4%BF%A1/"/>
    
    <category term="AI对话" scheme="https://blog.adoreorg.cn/tags/AI%E5%AF%B9%E8%AF%9D/"/>
    
    <category term="消息处理" scheme="https://blog.adoreorg.cn/tags/%E6%B6%88%E6%81%AF%E5%A4%84%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>派洞察 - 知识库检索模块</title>
    <link href="https://blog.adoreorg.cn/posts/f5e509b1.html"/>
    <id>https://blog.adoreorg.cn/posts/f5e509b1.html</id>
    <published>2025-07-19T16:00:00.000Z</published>
    <updated>2025-11-22T09:31:04.267Z</updated>
    
    <content type="html"><![CDATA[<h1>🎯 派洞察——科研文献智能 RAG 知识库系统</h1><h2 id="🔍-知识库检索模块">🔍 知识库检索模块</h2><blockquote><p><strong>模块定位</strong>：知识库检索模块是系统的核心检索引擎，提供基于混合检索技术的智能文档搜索功能，支持语义理解和权限控制。</p></blockquote><h3 id="🎯-核心目标">🎯 核心目标</h3><ul><li>✅ <strong>混合检索</strong>：结合关键词检索和语义向量检索，提供更精准的搜索结果</li><li>✅ <strong>智能理解</strong>：支持自然语言查询，理解用户搜索意图</li><li>✅ <strong>权限控制</strong>：根据用户身份和组织标签进行结果过滤</li><li>✅ <strong>高性能</strong>：优化检索性能，支持大规模文档库</li><li>✅ <strong>可扩展</strong>：支持多种检索算法和排序策略</li></ul><h1>📊 一、功能需求</h1><h2 id="🔍-混合检索">🔍 混合检索</h2><blockquote><p>🔤 <strong>功能描述</strong>：提供基于关键词和语义向量的混合检索功能，结合两种检索方式的优势，提供更精准的搜索结果。</p><p>🔧 <strong>检索功能</strong>:</p><ul><li>支持关键词全文检索</li><li>支持语义向量相似度检索</li><li>自动融合两种检索结果</li><li>支持自定义结果数量（topK参数）</li></ul></blockquote><h2 id="🛡️-权限控制">🛡️ 权限控制</h2><blockquote><p>🔐 <strong>功能描述</strong>：根据用户的登录状态和组织标签，对检索结果进行权限过滤，确保用户只能访问其有权限查看的内容。</p><p>🔧 <strong>权限功能</strong>:</p><ul><li>登录用户：返回用户有权限访问的所有内容</li><li>匿名用户：仅返回公开内容</li><li>组织标签过滤：根据用户组织标签筛选结果</li><li>私有内容保护：确保私有内容不被未授权访问</li></ul></blockquote><h2 id="📊-结果排序">📊 结果排序</h2><blockquote><p>📈 <strong>功能描述</strong>：对检索结果进行智能排序，将最相关的内容优先展示给用户。</p><p>🔧 <strong>排序策略</strong>:</p><ul><li>相关性评分排序</li><li>时间权重排序</li><li>用户行为权重</li><li>综合评分计算</li></ul></blockquote><h2 id="📝-结果格式化">📝 结果格式化</h2><blockquote><p>🎨 <strong>功能描述</strong>：将检索结果格式化为统一的响应结构，便于前端解析和展示。</p><p>🔧 <strong>格式功能</strong>:</p><ul><li>统一的JSON响应格式</li><li>包含文档元信息（文件MD5、分片ID等）</li><li>包含内容预览和评分</li><li>包含权限信息（用户ID、组织标签、公开状态）</li></ul></blockquote><h1>🛠️ 二、技术方案</h1><h2 id="🔍-混合检索技术">🔍 混合检索技术</h2><blockquote><p>🔤 <strong>关键词检索</strong>：使用全文检索引擎（如Elasticsearch）进行基于关键词的文档检索。</p><p>🎯 <strong>向量检索</strong>：使用向量数据库（如Milvus、Pinecone）进行基于语义向量的相似度检索。</p><p>🔄 <strong>结果融合</strong>：采用加权融合算法，将关键词检索和向量检索的结果进行智能合并。</p></blockquote><h2 id="🧠-语义理解">🧠 语义理解</h2><blockquote><p>📝 <strong>文本向量化</strong>：使用预训练语言模型（如BERT、Sentence-BERT）将文档和查询转换为向量表示。</p><p>🎯 <strong>相似度计算</strong>：使用余弦相似度等算法计算查询向量与文档向量的相似度。</p><p>🔧 <strong>语义增强</strong>：通过同义词扩展、查询重写等技术增强检索效果。</p></blockquote><h2 id="🔐-权限控制机制">🔐 权限控制机制</h2><blockquote><p>👤 <strong>用户身份验证</strong>：通过JWT token验证用户身份，获取用户ID和组织标签信息。</p><p>🏷️ <strong>组织标签过滤</strong>：根据用户的组织标签，过滤检索结果，确保内容安全。</p><p>🔒 <strong>权限层级</strong>：支持公开、组织内、私有等多层级权限控制。</p></blockquote><h2 id="📊-性能优化">📊 性能优化</h2><blockquote><p>⚡ <strong>缓存机制</strong>：使用Redis缓存热门查询和检索结果，减少重复计算。</p><p>🚀 <strong>异步处理</strong>：采用异步方式处理复杂的向量计算和结果融合。</p><p>📈 <strong>索引优化</strong>：优化数据库索引和向量索引，提升检索效率。</p></blockquote><h1>📝 三、关键流程</h1><h2 id="🔍-1-混合检索流程">🔍 1. 混合检索流程</h2><h3 id="🔄-流程步骤">🔄 流程步骤</h3><ol><li>🔍 <strong>接收查询</strong>：接收用户搜索查询字符串和topK参数</li><li>✅ <strong>身份验证</strong>：验证用户身份（如果有token）</li><li>🔤 <strong>关键词检索</strong>：执行基于关键词的全文检索</li><li>🎯 <strong>向量检索</strong>：执行基于语义向量的相似度检索</li><li>🔄 <strong>结果融合</strong>：将两种检索结果进行智能融合</li><li>🛡️ <strong>权限过滤</strong>：根据用户权限过滤检索结果</li><li>📊 <strong>结果排序</strong>：对融合后的结果进行相关性排序</li><li>📤 <strong>返回结果</strong>：返回格式化的检索结果</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="get/search/hybrid"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#get/search/hybrid-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#get/search/hybrid-2">请求参数</button></li><li class="tab"><button type="button" data-href="#get/search/hybrid-3">请求头</button></li><li class="tab"><button type="button" data-href="#get/search/hybrid-4">成功响应</button></li><li class="tab"><button type="button" data-href="#get/search/hybrid-5">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="get/search/hybrid-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET /api/v1/search/hybrid?query=人工智能的发展&amp;topK=10</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/search/hybrid-2"><p><strong>请求参数</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">query: String     // 搜索查询字符串（必需）</span><br><span class="line">topK: int        // 返回结果数量（可选，默认10）</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/search/hybrid-3"><p><strong>请求头（可选）</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Authorization: Bearer JWT_TOKEN</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/search/hybrid-4"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;success&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;fileMd5&quot;</span><span class="punctuation">:</span> <span class="string">&quot;abc123def456&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;chunkId&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;textContent&quot;</span><span class="punctuation">:</span> <span class="string">&quot;人工智能是未来科技发展的核心方向。&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;score&quot;</span><span class="punctuation">:</span> <span class="number">0.92</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;userId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;user123&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;orgTag&quot;</span><span class="punctuation">:</span> <span class="string">&quot;TECH_DEPT&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;isPublic&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;fileMd5&quot;</span><span class="punctuation">:</span> <span class="string">&quot;def456ghi789&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;chunkId&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;textContent&quot;</span><span class="punctuation">:</span> <span class="string">&quot;机器学习算法的不断进步推动了人工智能的发展。&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;score&quot;</span><span class="punctuation">:</span> <span class="number">0.88</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;userId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;user456&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;orgTag&quot;</span><span class="punctuation">:</span> <span class="string">&quot;RESEARCH_TEAM&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;isPublic&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/search/hybrid-5"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">500</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;检索失败: 错误信息&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="🔐-2-权限控制流程">🔐 2. 权限控制流程</h2><h3 id="🔄-流程步骤-2">🔄 流程步骤</h3><ol><li>🔍 <strong>Token提取</strong>：从请求头中提取JWT token（如果有）</li><li>✅ <strong>身份验证</strong>：验证token有效性，获取用户ID</li><li>🏷️ <strong>权限获取</strong>：获取用户的组织标签和角色信息</li><li>🔍 <strong>结果检索</strong>：执行混合检索获取初步结果</li><li>🛡️ <strong>权限过滤</strong>：根据用户权限过滤检索结果</li><li>📤 <strong>结果返回</strong>：返回用户有权限查看的内容</li></ol><h2 id="📊-3-结果融合流程">📊 3. 结果融合流程</h2><h3 id="🔄-流程步骤-3">🔄 流程步骤</h3><ol><li>📊 <strong>获取结果</strong>：获取关键词检索和向量检索的结果</li><li>⚖️ <strong>权重计算</strong>：为两种检索结果分配权重</li><li>🔄 <strong>结果合并</strong>：将两种结果按照权重进行融合</li><li>📈 <strong>重新排序</strong>：对融合后的结果进行统一排序</li><li>✂️ <strong>结果截断</strong>：根据topK参数截取最终结果</li></ol><h2 id="📝-4-日志记录流程">📝 4. 日志记录流程</h2><h3 id="🔄-流程步骤-4">🔄 流程步骤</h3><ol><li>🚀 <strong>开始监控</strong>：启动性能监控器</li><li>📝 <strong>业务日志</strong>：记录检索开始和参数信息</li><li>⚡ <strong>执行检索</strong>：执行混合检索操作</li><li>✅ <strong>操作记录</strong>：记录用户操作和结果状态</li><li>📊 <strong>性能记录</strong>：记录检索耗时和结果数量</li><li>🏁 <strong>结束监控</strong>：结束性能监控</li></ol><h1>🗃️ 四、依赖的数据结构设计</h1><h2 id="📁-1-文件主表-file-upload">📁 1. 文件主表 (file_upload)</h2><div class="tabs" id="file_upload"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#file_upload-1">字段设计</button></li><li class="tab"><button type="button" data-href="#file_upload-2">建表语句</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="file_upload-1"><p><strong>字段设计</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">| 🔑 字段 | 📋 类型 | 📝 描述 |</span><br><span class="line">| --- | --- | --- |</span><br><span class="line">| <span class="code">`id`</span> | INT | 🆔 主键，文件上传记录 ID |</span><br><span class="line">| <span class="code">`file_md5`</span> | VARCHAR(32) | 🔤 文件的MD5值，作为主键唯一标识文件 |</span><br><span class="line">| <span class="code">`file_name`</span> | VARCHAR(255) | 📄 文件的原始名称 |</span><br><span class="line">| <span class="code">`total_size`</span> | BIGINT | 📊 文件总大小(字节) |</span><br><span class="line">| <span class="code">`status`</span> | INT | 📊 文件上传状态：0-上传中，1-已完成 |</span><br><span class="line">| <span class="code">`user_id`</span> | VARCHAR(64) | 👤 上传用户的标识符 |</span><br><span class="line">| <span class="code">`org_tag`</span> | VARCHAR(50) | 🏷️ 文件所属组织标签 |</span><br><span class="line">| <span class="code">`is_public`</span> | BOOLEAN | 🌍 文件是否公开 |</span><br><span class="line">| <span class="code">`created_at`</span> | TIMESTAMP | 📅 文件上传创建时间 |</span><br><span class="line">| <span class="code">`merged_at`</span> | TIMESTAMP | 🔄 文件合并完成时间 |</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="file_upload-2"><p><strong>建表语句</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> file_upload (</span><br><span class="line">  file_md5 <span class="type">VARCHAR</span>(<span class="number">32</span>) <span class="keyword">PRIMARY KEY</span> COMMENT <span class="string">&#x27;文件的MD5值，作为主键唯一标识文件&#x27;</span>,</span><br><span class="line">  file_name <span class="type">VARCHAR</span>(<span class="number">255</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;文件的原始名称&#x27;</span>,</span><br><span class="line">  total_size <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;文件总大小(字节)&#x27;</span>,</span><br><span class="line">  status <span class="type">INT</span> <span class="keyword">NOT NULL</span> <span class="keyword">DEFAULT</span> <span class="number">0</span> COMMENT <span class="string">&#x27;文件上传状态：0-上传中，1-已完成&#x27;</span>,</span><br><span class="line">  user_id <span class="type">VARCHAR</span>(<span class="number">64</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;上传用户的标识符&#x27;</span>,</span><br><span class="line">  org_tag <span class="type">VARCHAR</span>(<span class="number">50</span>) COMMENT <span class="string">&#x27;文件所属组织标签&#x27;</span>,</span><br><span class="line">  is_public <span class="type">BOOLEAN</span> <span class="keyword">NOT NULL</span> <span class="keyword">DEFAULT</span> <span class="literal">FALSE</span> COMMENT <span class="string">&#x27;文件是否公开&#x27;</span>,</span><br><span class="line">  created_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;文件上传创建时间&#x27;</span>,</span><br><span class="line">  merged_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> <span class="keyword">ON</span> <span class="keyword">UPDATE</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;文件合并完成时间&#x27;</span></span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 COMMENT<span class="operator">=</span><span class="string">&#x27;文件上传记录表&#x27;</span>;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="📄-2-分片表-chunk-info">📄 2. 分片表 (chunk_info)</h2><div class="tabs" id="chunk_info"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#chunk_info-1">字段设计</button></li><li class="tab"><button type="button" data-href="#chunk_info-2">建表语句</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="chunk_info-1"><p><strong>字段设计</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">| 🔑 字段 | 📋 类型 | 📝 描述 |</span><br><span class="line">| --- | --- | --- |</span><br><span class="line">| <span class="code">`id`</span> | <span class="code">`BIGINT`</span> | 🆔 主键，分块记录唯一标识 |</span><br><span class="line">| <span class="code">`file_md5`</span> | <span class="code">`VARCHAR(32)`</span> | 🔤 关联的文件MD5值 |</span><br><span class="line">| <span class="code">`chunk_index`</span> | <span class="code">`INT`</span> | 🔢 分块序号 |</span><br><span class="line">| <span class="code">`chunk_md5`</span> | <span class="code">`VARCHAR(32)`</span> | 🔤 分块的MD5值 |</span><br><span class="line">| <span class="code">`storage_path`</span> | <span class="code">`VARCHAR(255)`</span> | 🔗 分块在存储系统中的路径 |</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="chunk_info-2"><p><strong>建表语句</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> chunk_info (</span><br><span class="line">  id <span class="type">BIGINT</span> AUTO_INCREMENT <span class="keyword">PRIMARY KEY</span> COMMENT <span class="string">&#x27;分块记录唯一标识&#x27;</span>,</span><br><span class="line">  file_md5 <span class="type">VARCHAR</span>(<span class="number">32</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;关联的文件MD5值&#x27;</span>,</span><br><span class="line">  chunk_index <span class="type">INT</span> <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;分块序号&#x27;</span>,</span><br><span class="line">  chunk_md5 <span class="type">VARCHAR</span>(<span class="number">32</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;分块的MD5值&#x27;</span>,</span><br><span class="line">  storage_path <span class="type">VARCHAR</span>(<span class="number">255</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;分块在存储系统中的路径&#x27;</span></span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 COMMENT<span class="operator">=</span><span class="string">&#x27;文件分块信息表&#x27;</span>;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="📄-3-解析结果表-document-vectors">📄 3. 解析结果表 (document_vectors)</h2><div class="tabs" id="document_vectors"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#document_vectors-1">字段设计</button></li><li class="tab"><button type="button" data-href="#document_vectors-2">建表语句</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="document_vectors-1"><p><strong>字段设计</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">| 🔑 字段 | 📋 类型 | 📝 描述 |</span><br><span class="line">| --- | --- | --- |</span><br><span class="line">| <span class="code">`vector_id`</span> | <span class="code">`BIGINT`</span> | 🆔 向量记录唯一标识 |</span><br><span class="line">| <span class="code">`file_md5`</span> | <span class="code">`VARCHAR(32)`</span> | 🔤 关联的文件MD5值 |</span><br><span class="line">| <span class="code">`chunk_id`</span> | <span class="code">`INT`</span> | 🔢 文本分块序号 |</span><br><span class="line">| <span class="code">`text_content`</span> | <span class="code">`TEXT`</span> | 📄 文本内容 |</span><br><span class="line">| <span class="code">`model_version`</span> | <span class="code">`VARCHAR(32)`</span> | 🔤 向量模型版本 |</span><br><span class="line">| <span class="code">`user_id`</span> | <span class="code">`VARCHAR(64)`</span> | 👤 上传用户ID |</span><br><span class="line">| <span class="code">`org_tag`</span> | <span class="code">`VARCHAR(50)`</span> | 🏷️ 文件所属组织标签 |</span><br><span class="line">| <span class="code">`is_public`</span> | <span class="code">`BOOLEAN`</span> | 🌍 文件是否公开 |</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="document_vectors-2"><p><strong>建表语句</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> document_vectors (</span><br><span class="line">  vector_id <span class="type">BIGINT</span> AUTO_INCREMENT <span class="keyword">PRIMARY KEY</span> COMMENT <span class="string">&#x27;向量记录唯一标识&#x27;</span>,</span><br><span class="line">  file_md5 <span class="type">VARCHAR</span>(<span class="number">32</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;关联的文件MD5值&#x27;</span>,</span><br><span class="line">  chunk_id <span class="type">INT</span> <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;文本分块序号&#x27;</span>,</span><br><span class="line">  text_content TEXT COMMENT <span class="string">&#x27;文本内容&#x27;</span>,</span><br><span class="line">  model_version <span class="type">VARCHAR</span>(<span class="number">32</span>) COMMENT <span class="string">&#x27;向量模型版本&#x27;</span>,</span><br><span class="line">  user_id <span class="type">VARCHAR</span>(<span class="number">64</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;上传用户ID&#x27;</span>,</span><br><span class="line">  org_tag <span class="type">VARCHAR</span>(<span class="number">50</span>) COMMENT <span class="string">&#x27;文件所属组织标签&#x27;</span>,</span><br><span class="line">  is_public <span class="type">BOOLEAN</span> <span class="keyword">NOT NULL</span> <span class="keyword">DEFAULT</span> <span class="literal">FALSE</span> COMMENT <span class="string">&#x27;文件是否公开&#x27;</span></span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 COMMENT<span class="operator">=</span><span class="string">&#x27;文档向量存储表&#x27;</span>;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div>]]></content>
    
    
    <summary type="html">科研文献智能 RAG 知识库系统知识库检索模块详细设计文档</summary>
    
    
    
    <category term="学海拾贝" scheme="https://blog.adoreorg.cn/categories/%E5%AD%A6%E6%B5%B7%E6%8B%BE%E8%B4%9D/"/>
    
    
    <category term="知识库检索" scheme="https://blog.adoreorg.cn/tags/%E7%9F%A5%E8%AF%86%E5%BA%93%E6%A3%80%E7%B4%A2/"/>
    
    <category term="混合检索" scheme="https://blog.adoreorg.cn/tags/%E6%B7%B7%E5%90%88%E6%A3%80%E7%B4%A2/"/>
    
    <category term="语义搜索" scheme="https://blog.adoreorg.cn/tags/%E8%AF%AD%E4%B9%89%E6%90%9C%E7%B4%A2/"/>
    
    <category term="向量检索" scheme="https://blog.adoreorg.cn/tags/%E5%90%91%E9%87%8F%E6%A3%80%E7%B4%A2/"/>
    
    <category term="权限控制" scheme="https://blog.adoreorg.cn/tags/%E6%9D%83%E9%99%90%E6%8E%A7%E5%88%B6/"/>
    
  </entry>
  
  <entry>
    <title>派洞察 - 文件上传模块</title>
    <link href="https://blog.adoreorg.cn/posts/8c42ef91.html"/>
    <id>https://blog.adoreorg.cn/posts/8c42ef91.html</id>
    <published>2025-07-14T16:00:00.000Z</published>
    <updated>2025-11-22T09:17:34.913Z</updated>
    
    <content type="html"><![CDATA[<h1>🎯 派洞察——科研文献智能 RAG 知识库系统</h1><h2 id="📁-文件上传模块">📁 文件上传模块</h2><blockquote><p><strong>模块定位</strong>：文件上传模块是系统的核心功能模块，负责处理科研文献文件的上传、分片处理、类型验证和权限控制。</p></blockquote><h3 id="🎯-核心目标">🎯 核心目标</h3><ul><li>✅ <strong>高效上传</strong>：支持大文件分片上传，提供上传进度反馈</li><li>✅ <strong>类型验证</strong>：严格的文件类型验证，确保上传文档符合系统要求</li><li>✅ <strong>权限控制</strong>：基于组织标签和公开权限的文件访问控制</li><li>✅ <strong>异步处理</strong>：通过 Kafka 消息队列实现文件的异步处理</li><li>✅ <strong>数据安全</strong>：完整的权限验证和数据隔离机制</li></ul><h1>📊 一、功能需求</h1><h2 id="📤-文件分片上传">📤 文件分片上传</h2><blockquote><p>📝 <strong>功能描述</strong>：支持大文件的分片上传，客户端可将大文件分割成多个分片依次上传，服务端负责分片的接收和存储。</p><p>⚡ <strong>进度反馈</strong>：实时返回已上传分片列表和上传进度百分比。</p><p>🔍 <strong>类型验证</strong>：在第一个分片上传时进行文件类型验证，确保文件类型符合系统要求。</p></blockquote><h2 id="📋-上传状态查询">📋 上传状态查询</h2><blockquote><p>🔍 <strong>状态查询</strong>：客户端可查询指定文件的上传状态，包括已上传分片列表、总进度、文件名和文件类型等信息。</p><p>📊 <strong>进度计算</strong>：服务端根据已上传分片数量和总分片数量计算上传进度百分比。</p></blockquote><h2 id="🔗-文件分片合并">🔗 文件分片合并</h2><blockquote><p>🧩 <strong>分片合并</strong>：当所有分片上传完成后，客户端可触发文件合并操作，服务端将分片合并成完整文件。</p><p>🔒 <strong>权限验证</strong>：合并前验证用户对该文件的操作权限，确保只有文件所有者才能合并文件。</p><p>📋 <strong>完整性检查</strong>：检查所有分片是否已上传完成，未完成则拒绝合并请求。</p></blockquote><h2 id="🏷️-组织标签与权限">🏷️ 组织标签与权限</h2><blockquote><p>🆔 <strong>自动标签</strong>：如未指定组织标签，自动使用用户的主组织标签（primaryOrg）。</p><p>🔓 <strong>公开权限</strong>：支持设置文件公开权限（isPublic），公开文件不受组织标签限制。</p><p>🛡️ <strong>权限继承</strong>：文件处理任务包含完整的权限信息（用户ID、组织标签、公开权限）。</p></blockquote><h2 id="📄-文件类型验证">📄 文件类型验证</h2><blockquote><p>✅ <strong>类型检查</strong>：系统仅支持特定类型的文档文件（如PDF、Word、TXT等）。</p><p>🚫 <strong>拒绝机制</strong>：不支持的文件类型将被拒绝上传，并返回详细的错误信息和支持的文件类型列表。</p><p>📋 <strong>扩展支持</strong>：提供接口获取系统支持的文件类型和扩展名列表。</p></blockquote><h2 id="🚀-异步任务处理">🚀 异步任务处理</h2><blockquote><p>📨 <strong>Kafka集成</strong>：文件合并成功后，发送任务到 Kafka 消息队列进行异步处理。</p><p>🔄 <strong>事务保证</strong>：使用 Kafka 事务确保消息发送的可靠性。</p><p>📊 <strong>任务信息</strong>：任务包含文件MD5、对象URL、文件名、用户ID、组织标签和公开权限等完整信息。</p></blockquote><h1>🛠️ 二、技术方案</h1><h2 id="📤-分片上传技术">📤 分片上传技术</h2><blockquote><p>🎯 <strong>分片策略</strong>：客户端将大文件分割成固定大小的分片（如5MB），依次上传每个分片。</p><p>🗂️ <strong>分片存储</strong>：服务端将分片临时存储，记录已上传分片索引。</p><p>📊 <strong>进度跟踪</strong>：维护分片上传状态，实时计算和返回上传进度。</p></blockquote><h2 id="🔐-身份认证与授权">🔐 身份认证与授权</h2><blockquote><p>🛡️ <strong>JWT认证</strong>：基于 JWT token 验证用户身份，确保上传操作的安全性。</p><p>👤 <strong>用户属性</strong>：通过 @RequestAttribute(“userId”) 获取当前用户ID，由拦截器提前解析。</p><p>🔒 <strong>权限控制</strong>：基于用户ID验证文件操作权限，防止越权访问。</p></blockquote><h2 id="📊-数据持久化">📊 数据持久化</h2><blockquote><p>🗄️ <strong>MySQL</strong>：存储文件上传记录、文件元数据和权限信息。</p><p>⚡ <strong>Redis</strong>：缓存上传状态信息，提高查询性能。</p><p>🚀 <strong>Spring Data JPA</strong>：简化数据库操作，提供文件记录的CRUD操作。</p></blockquote><h2 id="🚀-消息队列集成">🚀 消息队列集成</h2><blockquote><p>📨 <strong>Kafka配置</strong>：通过 KafkaConfig 获取文件处理主题名称。</p><p>🔄 <strong>事务处理</strong>：使用 kafkaTemplate.executeInTransaction 确保消息发送的事务性。</p><p>📋 <strong>任务模型</strong>：FileProcessingTask 包含文件处理所需的完整信息。</p></blockquote><h2 id="🧰-辅助工具">🧰 辅助工具</h2><blockquote><p>📊 <strong>性能监控</strong>：LogUtils.PerformanceMonitor 监控接口性能。</p><p>📝 <strong>业务日志</strong>：LogUtils.logBusiness 记录业务操作日志。</p><p>⚠️ <strong>错误日志</strong>：LogUtils.logBusinessError 记录错误信息和异常。</p><p>📁 <strong>文件工具</strong>：getFileType 方法提取文件类型，支持常见文档格式。</p></blockquote><h1>📝 三、关键流程</h1><h2 id="📤-1-文件分片上传">📤 1. 文件分片上传</h2><h3 id="🔄-流程步骤">🔄 流程步骤</h3><ol><li>📨 <strong>接收请求</strong>：客户端发送分片上传请求，包含文件MD5、分片索引、文件信息等参数</li><li>✅ <strong>类型验证</strong>：第一个分片进行文件类型验证，检查文件扩展名和内容类型</li><li>🏷️ <strong>组织标签</strong>：如未指定orgTag，获取用户主组织标签</li><li>💾 <strong>分片存储</strong>：调用uploadService.uploadChunk存储分片数据</li><li>📊 <strong>状态查询</strong>：查询已上传分片列表和总分片数量</li><li>🧮 <strong>进度计算</strong>：计算并返回上传进度百分比</li><li>📤 <strong>返回结果</strong>：返回统一格式的响应数据</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="post/chunk"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#post/chunk-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#post/chunk-2">请求参数</button></li><li class="tab"><button type="button" data-href="#post/chunk-3">成功响应</button></li><li class="tab"><button type="button" data-href="#post/chunk-4">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="post/chunk-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">POST /api/v1/upload/chunk</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/chunk-2"><p><strong>请求参数</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">fileMd5: string          // 文件的MD5值，用于唯一标识文件</span><br><span class="line">chunkIndex: int          // 分片索引，表示当前分片的位置</span><br><span class="line">totalSize: long          // 文件总大小</span><br><span class="line">fileName: string         // 文件名</span><br><span class="line">totalChunks: int         // 总分片数量（可选）</span><br><span class="line">orgTag: string           // 组织标签，如果未指定则使用用户的主组织标签（可选）</span><br><span class="line">isPublic: boolean        // 是否公开，默认为false（可选）</span><br><span class="line">file: MultipartFile      // 分片文件对象</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/chunk-3"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;分片上传成功&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;uploaded&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="number">0</span><span class="punctuation">,</span> <span class="number">1</span><span class="punctuation">,</span> <span class="number">2</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;progress&quot;</span><span class="punctuation">:</span> <span class="number">60.0</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/chunk-4"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">400</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;不支持的文件类型: .exe&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;fileType&quot;</span><span class="punctuation">:</span> <span class="string">&quot;EXECUTABLE&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;supportedTypes&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;PDF&quot;</span><span class="punctuation">,</span> <span class="string">&quot;DOCX&quot;</span><span class="punctuation">,</span> <span class="string">&quot;TXT&quot;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="📊-2-获取文件上传状态">📊 2. 获取文件上传状态</h2><h3 id="🔄-流程步骤-2">🔄 流程步骤</h3><ol><li>🔍 <strong>接收参数</strong>：获取文件MD5和用户ID参数</li><li>📋 <strong>查询信息</strong>：查询文件上传记录和已上传分片信息</li><li>🧮 <strong>计算进度</strong>：根据已上传分片计算上传进度</li><li>📤 <strong>返回状态</strong>：返回文件状态、进度和基本信息</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="get/status"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#get/status-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#get/status-2">成功响应</button></li><li class="tab"><button type="button" data-href="#get/status-3">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="get/status-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET /api/v1/upload/status?file_md5=&#123;fileMd5&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/status-2"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;获取上传状态成功&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;uploaded&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="number">0</span><span class="punctuation">,</span> <span class="number">1</span><span class="punctuation">,</span> <span class="number">2</span><span class="punctuation">,</span> <span class="number">3</span><span class="punctuation">,</span> <span class="number">4</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;progress&quot;</span><span class="punctuation">:</span> <span class="number">100.0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;fileName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;research_paper.pdf&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;fileType&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PDF&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/status-3"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">500</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;获取文件上传状态失败: 数据库连接异常&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="🔗-3-合并文件分片">🔗 3. 合并文件分片</h2><h3 id="🔄-流程步骤-3">🔄 流程步骤</h3><ol><li>📨 <strong>接收请求</strong>：接收文件合并请求，包含文件MD5和文件名</li><li>🔒 <strong>权限验证</strong>：验证用户对该文件的操作权限</li><li>📋 <strong>完整性检查</strong>：检查所有分片是否已上传完成</li><li>🧩 <strong>文件合并</strong>：调用uploadService.mergeChunks合并分片</li><li>📨 <strong>发送任务</strong>：构建FileProcessingTask并发送到Kafka</li><li>📤 <strong>返回结果</strong>：返回合并成功和文件访问URL</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="post/merge"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#post/merge-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#post/merge-2">请求体</button></li><li class="tab"><button type="button" data-href="#post/merge-3">成功响应</button></li><li class="tab"><button type="button" data-href="#post/merge-4">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="post/merge-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">POST /api/v1/upload/merge</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/merge-2"><p><strong>请求体（JSON）</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;fileMd5&quot;</span><span class="punctuation">:</span> <span class="string">&quot;abc123def456&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;fileName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;research_paper.pdf&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/merge-3"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;文件合并成功，任务已发送到 Kafka&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;object_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://storage.example.com/files/abc123def456.pdf&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/merge-4"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">403</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;没有权限操作此文件&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="📋-4-获取支持的文件类型">📋 4. 获取支持的文件类型</h2><h3 id="🔄-流程步骤-4">🔄 流程步骤</h3><ol><li>📋 <strong>查询类型</strong>：调用fileTypeValidationService获取支持的文件类型</li><li>📊 <strong>构建数据</strong>：构建包含文件类型、扩展名和描述信息的响应数据</li><li>📤 <strong>返回结果</strong>：返回支持的文件类型列表</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="get/supported-types"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#get/supported-types-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#get/supported-types-2">成功响应</button></li><li class="tab"><button type="button" data-href="#get/supported-types-3">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="get/supported-types-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET /api/v1/upload/supported-types</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/supported-types-2"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;获取支持的文件类型成功&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;supportedTypes&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;PDF&quot;</span><span class="punctuation">,</span> <span class="string">&quot;DOCX&quot;</span><span class="punctuation">,</span> <span class="string">&quot;DOC&quot;</span><span class="punctuation">,</span> <span class="string">&quot;TXT&quot;</span><span class="punctuation">,</span> <span class="string">&quot;PPTX&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;supportedExtensions&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;.pdf&quot;</span><span class="punctuation">,</span> <span class="string">&quot;.docx&quot;</span><span class="punctuation">,</span> <span class="string">&quot;.doc&quot;</span><span class="punctuation">,</span> <span class="string">&quot;.txt&quot;</span><span class="punctuation">,</span> <span class="string">&quot;.pptx&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;系统支持的文档类型文件，这些文件可以被解析并进行向量化处理&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/supported-types-3"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">500</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;获取支持的文件类型失败&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h1>🗃️ 四、库表设计</h1><h2 id="📁-1-文件主表-file-upload">📁 1. 文件主表 (file_upload)</h2><div class="tabs" id="file_upload"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#file_upload-1">字段设计</button></li><li class="tab"><button type="button" data-href="#file_upload-2">建表语句</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="file_upload-1"><p><strong>字段设计</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">| 🔑 字段 | 📋 类型 | 📝 描述 |</span><br><span class="line">| --- | --- | --- |</span><br><span class="line">| <span class="code">`id`</span> | INT | 🆔 主键，文件上传记录 ID |</span><br><span class="line">| <span class="code">`file_md5`</span> | VARCHAR(32) | 🔤 文件的MD5值，作为主键唯一标识文件 |</span><br><span class="line">| <span class="code">`file_name`</span> | VARCHAR(255) | 📄 文件的原始名称 |</span><br><span class="line">| <span class="code">`total_size`</span> | BIGINT | 📊 文件总大小(字节) |</span><br><span class="line">| <span class="code">`status`</span> | INT | 📊 文件上传状态：0-上传中，1-已完成 |</span><br><span class="line">| <span class="code">`user_id`</span> | VARCHAR(64) | 👤 上传用户的标识符 |</span><br><span class="line">| <span class="code">`org_tag`</span> | VARCHAR(50) | 🏷️ 文件所属组织标签 |</span><br><span class="line">| <span class="code">`is_public`</span> | BOOLEAN | 🌍 文件是否公开 |</span><br><span class="line">| <span class="code">`created_at`</span> | TIMESTAMP | 📅 文件上传创建时间 |</span><br><span class="line">| <span class="code">`merged_at`</span> | TIMESTAMP | 🔄 文件合并完成时间 |</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="file_upload-2"><p><strong>建表语句</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> file_upload (</span><br><span class="line">  file_md5 <span class="type">VARCHAR</span>(<span class="number">32</span>) <span class="keyword">PRIMARY KEY</span> COMMENT <span class="string">&#x27;文件的MD5值，作为主键唯一标识文件&#x27;</span>,</span><br><span class="line">  file_name <span class="type">VARCHAR</span>(<span class="number">255</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;文件的原始名称&#x27;</span>,</span><br><span class="line">  total_size <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;文件总大小(字节)&#x27;</span>,</span><br><span class="line">  status <span class="type">INT</span> <span class="keyword">NOT NULL</span> <span class="keyword">DEFAULT</span> <span class="number">0</span> COMMENT <span class="string">&#x27;文件上传状态：0-上传中，1-已完成&#x27;</span>,</span><br><span class="line">  user_id <span class="type">VARCHAR</span>(<span class="number">64</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;上传用户的标识符&#x27;</span>,</span><br><span class="line">  org_tag <span class="type">VARCHAR</span>(<span class="number">50</span>) COMMENT <span class="string">&#x27;文件所属组织标签&#x27;</span>,</span><br><span class="line">  is_public <span class="type">BOOLEAN</span> <span class="keyword">NOT NULL</span> <span class="keyword">DEFAULT</span> <span class="literal">FALSE</span> COMMENT <span class="string">&#x27;文件是否公开&#x27;</span>,</span><br><span class="line">  created_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;文件上传创建时间&#x27;</span>,</span><br><span class="line">  merged_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> <span class="keyword">ON</span> <span class="keyword">UPDATE</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;文件合并完成时间&#x27;</span></span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 COMMENT<span class="operator">=</span><span class="string">&#x27;文件上传记录表&#x27;</span>;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="📄-2-分片表-chunk-info">📄 2. 分片表 (chunk_info)</h2><div class="tabs" id="chunk_info"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#chunk_info-1">字段设计</button></li><li class="tab"><button type="button" data-href="#chunk_info-2">建表语句</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="chunk_info-1"><p><strong>字段设计</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">| 🔑 字段 | 📋 类型 | 📝 描述 |</span><br><span class="line">| --- | --- | --- |</span><br><span class="line">| <span class="code">`id`</span> | <span class="code">`BIGINT`</span> | 🆔 主键，分块记录唯一标识 |</span><br><span class="line">| <span class="code">`file_md5`</span> | <span class="code">`VARCHAR(32)`</span> | 🔤 关联的文件MD5值 |</span><br><span class="line">| <span class="code">`chunk_index`</span> | <span class="code">`INT`</span> | 🔢 分块序号 |</span><br><span class="line">| <span class="code">`chunk_md5`</span> | <span class="code">`VARCHAR(32)`</span> | 🔤 分块的MD5值 |</span><br><span class="line">| <span class="code">`storage_path`</span> | <span class="code">`VARCHAR(255)`</span> | 🔗 分块在存储系统中的路径 |</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="chunk_info-2"><p><strong>建表语句</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> chunk_info (</span><br><span class="line">  id <span class="type">BIGINT</span> AUTO_INCREMENT <span class="keyword">PRIMARY KEY</span> COMMENT <span class="string">&#x27;分块记录唯一标识&#x27;</span>,</span><br><span class="line">  file_md5 <span class="type">VARCHAR</span>(<span class="number">32</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;关联的文件MD5值&#x27;</span>,</span><br><span class="line">  chunk_index <span class="type">INT</span> <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;分块序号&#x27;</span>,</span><br><span class="line">  chunk_md5 <span class="type">VARCHAR</span>(<span class="number">32</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;分块的MD5值&#x27;</span>,</span><br><span class="line">  storage_path <span class="type">VARCHAR</span>(<span class="number">255</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;分块在存储系统中的路径&#x27;</span></span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 COMMENT<span class="operator">=</span><span class="string">&#x27;文件分块信息表&#x27;</span>;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="📄-3-解析结果表-document-vectors">📄 3. 解析结果表 (document_vectors)</h2><div class="tabs" id="document_vectors"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#document_vectors-1">字段设计</button></li><li class="tab"><button type="button" data-href="#document_vectors-2">建表语句</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="document_vectors-1"><p><strong>字段设计</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">| 🔑 字段 | 📋 类型 | 📝 描述 |</span><br><span class="line">| --- | --- | --- |</span><br><span class="line">| <span class="code">`vector_id`</span> | <span class="code">`BIGINT`</span> | 🆔 向量记录唯一标识 |</span><br><span class="line">| <span class="code">`file_md5`</span> | <span class="code">`VARCHAR(32)`</span> | 🔤 关联的文件MD5值 |</span><br><span class="line">| <span class="code">`chunk_id`</span> | <span class="code">`INT`</span> | 🔢 文本分块序号 |</span><br><span class="line">| <span class="code">`text_content`</span> | <span class="code">`TEXT`</span> | 📄 文本内容 |</span><br><span class="line">| <span class="code">`model_version`</span> | <span class="code">`VARCHAR(32)`</span> | 🔤 向量模型版本 |</span><br><span class="line">| <span class="code">`user_id`</span> | <span class="code">`VARCHAR(64)`</span> | 👤 上传用户ID |</span><br><span class="line">| <span class="code">`org_tag`</span> | <span class="code">`VARCHAR(50)`</span> | 🏷️ 文件所属组织标签 |</span><br><span class="line">| <span class="code">`is_public`</span> | <span class="code">`BOOLEAN`</span> | 🌍 文件是否公开 |</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="document_vectors-2"><p><strong>建表语句</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> document_vectors (</span><br><span class="line">  vector_id <span class="type">BIGINT</span> AUTO_INCREMENT <span class="keyword">PRIMARY KEY</span> COMMENT <span class="string">&#x27;向量记录唯一标识&#x27;</span>,</span><br><span class="line">  file_md5 <span class="type">VARCHAR</span>(<span class="number">32</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;关联的文件MD5值&#x27;</span>,</span><br><span class="line">  chunk_id <span class="type">INT</span> <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;文本分块序号&#x27;</span>,</span><br><span class="line">  text_content TEXT COMMENT <span class="string">&#x27;文本内容&#x27;</span>,</span><br><span class="line">  model_version <span class="type">VARCHAR</span>(<span class="number">32</span>) COMMENT <span class="string">&#x27;向量模型版本&#x27;</span>,</span><br><span class="line">  user_id <span class="type">VARCHAR</span>(<span class="number">64</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;上传用户ID&#x27;</span>,</span><br><span class="line">  org_tag <span class="type">VARCHAR</span>(<span class="number">50</span>) COMMENT <span class="string">&#x27;文件所属组织标签&#x27;</span>,</span><br><span class="line">  is_public <span class="type">BOOLEAN</span> <span class="keyword">NOT NULL</span> <span class="keyword">DEFAULT</span> <span class="literal">FALSE</span> COMMENT <span class="string">&#x27;文件是否公开&#x27;</span></span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 COMMENT<span class="operator">=</span><span class="string">&#x27;文档向量存储表&#x27;</span>;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div>]]></content>
    
    
    <summary type="html">科研文献智能 RAG 知识库系统文件上传模块详细设计文档</summary>
    
    
    
    <category term="学海拾贝" scheme="https://blog.adoreorg.cn/categories/%E5%AD%A6%E6%B5%B7%E6%8B%BE%E8%B4%9D/"/>
    
    
    <category term="权限控制" scheme="https://blog.adoreorg.cn/tags/%E6%9D%83%E9%99%90%E6%8E%A7%E5%88%B6/"/>
    
    <category term="文件上传" scheme="https://blog.adoreorg.cn/tags/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0/"/>
    
    <category term="分片上传" scheme="https://blog.adoreorg.cn/tags/%E5%88%86%E7%89%87%E4%B8%8A%E4%BC%A0/"/>
    
    <category term="Kafka消息队列" scheme="https://blog.adoreorg.cn/tags/Kafka%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/"/>
    
    <category term="文件类型验证" scheme="https://blog.adoreorg.cn/tags/%E6%96%87%E4%BB%B6%E7%B1%BB%E5%9E%8B%E9%AA%8C%E8%AF%81/"/>
    
    <category term="组织标签" scheme="https://blog.adoreorg.cn/tags/%E7%BB%84%E7%BB%87%E6%A0%87%E7%AD%BE/"/>
    
  </entry>
  
  <entry>
    <title>派洞察 - 管理员模块</title>
    <link href="https://blog.adoreorg.cn/posts/f99b1fcd.html"/>
    <id>https://blog.adoreorg.cn/posts/f99b1fcd.html</id>
    <published>2025-07-09T16:00:00.000Z</published>
    <updated>2025-11-21T14:09:36.534Z</updated>
    
    <content type="html"><![CDATA[<h1>🎯 派洞察——科研文献智能 RAG 知识库系统</h1><h2 id="🛡️-管理员模块">🛡️ 管理员模块</h2><blockquote><p><strong>模块定位</strong>：管理员模块是系统的核心管理模块，负责处理系统管理、用户管理、知识库管理和系统监控等高级功能。</p></blockquote><h3 id="🎯-核心目标">🎯 核心目标</h3><ul><li>✅ <strong>系统管理</strong>：提供系统级别的配置和管理功能</li><li>✅ <strong>用户管控</strong>：管理所有用户账户，包括创建、修改、权限分配等</li><li>✅ <strong>知识库维护</strong>：管理知识库文档，包括添加、删除、分类等</li><li>✅ <strong>系统监控</strong>：实时监控系统状态和用户活动</li><li>✅ <strong>安全保障</strong>：确保系统运行的安全性和稳定性</li></ul><h1>📊 一、功能需求</h1><h2 id="👥-用户管理">👥 用户管理</h2><blockquote><p>📝 <strong>功能描述</strong>：管理员可以查看、创建、修改和删除用户账户，分配组织标签和管理权限。</p><p>🔧 <strong>管理功能</strong>：</p><ul><li>查看所有用户列表和详细信息</li><li>创建新的管理员用户</li><li>为用户分配组织标签</li><li>管理用户权限和状态</li></ul></blockquote><h2 id="📚-知识库管理">📚 知识库管理</h2><blockquote><p>📖 <strong>功能描述</strong>：管理员可以管理知识库文档，包括添加新文档、删除旧文档等维护操作。</p><p>🔧 <strong>管理功能</strong>：</p><ul><li>添加新的知识库文档</li><li>删除不需要的文档</li><li>管理文档描述和分类</li></ul></blockquote><h2 id="📊-系统监控">📊 系统监控</h2><blockquote><p>📈 <strong>功能描述</strong>：实时监控系统的运行状态，包括资源使用情况和用户活动。</p><p>🔧 <strong>监控内容</strong>：</p><ul><li>CPU、内存、磁盘使用率</li><li>活跃用户数</li><li>文档总数和对话总数</li><li>用户活动日志</li></ul></blockquote><h2 id="🏷️-组织标签管理">🏷️ 组织标签管理</h2><blockquote><p>🆔 <strong>功能描述</strong>：管理员可以创建、修改、删除组织标签，构建组织标签树结构。</p><p>🔧 <strong>管理功能</strong>：</p><ul><li>创建新的组织标签</li><li>更新现有标签信息</li><li>删除不需要的标签</li><li>获取标签树结构</li></ul></blockquote><h2 id="💬-对话历史管理">💬 对话历史管理</h2><blockquote><p>📝 <strong>功能描述</strong>：管理员可以查看和查询所有用户的对话历史，支持按用户和时间范围筛选。</p><p>🔧 <strong>查询功能</strong>：</p><ul><li>查看所有用户的对话历史</li><li>按指定用户筛选对话</li><li>按时间范围筛选对话</li><li>导出对话记录</li></ul></blockquote><h1>🛠️ 二、技术方案</h1><h2 id="🔐-身份认证与授权">🔐 身份认证与授权</h2><blockquote><p>🛡️ <strong>管理员验证</strong>：所有管理员接口都需要验证用户的管理员身份，确保只有管理员才能访问。</p><p>🔑 <strong>JWT认证</strong>：使用JWT token进行身份认证，从请求头中提取并验证token。</p><p>🎯 <strong>权限控制</strong>：基于角色的访问控制（RBAC），只有ADMIN角色的用户才能访问管理员接口。</p></blockquote><h2 id="💾-数据持久化">💾 数据持久化</h2><blockquote><p>🗄️ <strong>MySQL</strong>：存储用户信息、组织标签、知识库文档信息等结构化数据。</p><p>⚡ <strong>Redis</strong>：缓存用户会话信息、对话历史、系统状态等实时数据。</p><p>🚀 <strong>Spring Data JPA</strong>：提供数据库操作的ORM支持，简化数据访问层开发。</p></blockquote><h2 id="🧰-辅助工具">🧰 辅助工具</h2><blockquote><p>🔤 <strong>Apache Commons Lang3</strong>：提供字符串处理、集合操作等工具方法。</p><p>📝 <strong>Jackson</strong>：JSON序列化和反序列化，处理API请求响应数据。</p><p>📊 <strong>LogUtils</strong>：统一的日志记录工具，记录业务操作和性能监控。</p></blockquote><h2 id="🔍-系统监控">🔍 系统监控</h2><blockquote><p>📈 <strong>性能监控</strong>：使用LogUtils.PerformanceMonitor监控系统接口性能。</p><p>📝 <strong>业务日志</strong>：记录所有管理员操作的详细日志，便于审计和问题追踪。</p><p>⚠️ <strong>错误处理</strong>：统一的异常处理机制，确保系统稳定性。</p></blockquote><h1>📝 三、关键流程</h1><h2 id="📋-1-获取所有用户列表">📋 1. 获取所有用户列表</h2><h3 id="🔄-流程步骤">🔄 流程步骤</h3><ol><li>🔍 <strong>提取Token</strong>：从Authorization头中提取Bearer token</li><li>✅ <strong>验证管理员</strong>：验证当前用户是否为管理员</li><li>📊 <strong>查询用户</strong>：调用userRepository.findAll()获取所有用户</li><li>🛡️ <strong>数据脱敏</strong>：移除用户密码等敏感信息</li><li>📤 <strong>返回结果</strong>：返回用户列表数据</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="get/admin/users"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#get/admin/users-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#get/admin/users-2">请求头</button></li><li class="tab"><button type="button" data-href="#get/admin/users-3">成功响应</button></li><li class="tab"><button type="button" data-href="#get/admin/users-4">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="get/admin/users-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET /api/v1/admin/users</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/admin/users-2"><p><strong>请求头</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Authorization: Bearer JWT_TOKEN</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/admin/users-3"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Get all users successful&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;user1&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;role&quot;</span><span class="punctuation">:</span> <span class="string">&quot;USER&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;orgTags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;PRIVATE_user1&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;primaryOrg&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PRIVATE_user1&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/admin/users-4"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">500</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Failed to get users: error message&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="📚-2-添加知识库文档">📚 2. 添加知识库文档</h2><h3 id="🔄-流程步骤-2">🔄 流程步骤</h3><ol><li>📤 <strong>接收请求</strong>：接收文件和描述信息</li><li>✅ <strong>验证管理员</strong>：验证当前用户的管理员身份</li><li>📁 <strong>文件处理</strong>：处理上传的文档文件</li><li>💾 <strong>存储文档</strong>：将文档信息保存到知识库</li><li>📝 <strong>记录日志</strong>：记录添加文档的操作日志</li><li>📤 <strong>返回结果</strong>：返回操作成功信息</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="post/admin/knowledge/add"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#post/admin/knowledge/add-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#post/admin/knowledge/add-2">请求参数</button></li><li class="tab"><button type="button" data-href="#post/admin/knowledge/add-3">成功响应</button></li><li class="tab"><button type="button" data-href="#post/admin/knowledge/add-4">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="post/admin/knowledge/add-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">POST /api/v1/admin/knowledge/add</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/admin/knowledge/add-2"><p><strong>请求参数</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">file: MultipartFile        // 文档文件</span><br><span class="line">description: String        // 文档描述</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/admin/knowledge/add-3"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;文档已成功添加到知识库&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/admin/knowledge/add-4"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="string">&quot;添加文档失败: error message&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="🗑️-3-删除知识库文档">🗑️ 3. 删除知识库文档</h2><h3 id="🔄-流程步骤-3">🔄 流程步骤</h3><ol><li>📋 <strong>接收参数</strong>：接收文档ID参数</li><li>✅ <strong>验证管理员</strong>：验证当前用户的管理员身份</li><li>🔍 <strong>查找文档</strong>：验证文档是否存在</li><li>🗑️ <strong>删除文档</strong>：从知识库中删除指定文档</li><li>📝 <strong>记录日志</strong>：记录删除文档的操作日志</li><li>📤 <strong>返回结果</strong>：返回删除成功信息</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="delete/admin/knowledge/{documentid}"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#delete/admin/knowledge/{documentid}-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#delete/admin/knowledge/{documentid}-2">路径参数</button></li><li class="tab"><button type="button" data-href="#delete/admin/knowledge/{documentid}-3">成功响应</button></li><li class="tab"><button type="button" data-href="#delete/admin/knowledge/{documentid}-4">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="delete/admin/knowledge/{documentid}-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DELETE /api/v1/admin/knowledge/&#123;documentId&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="delete/admin/knowledge/{documentid}-2"><p><strong>路径参数</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">documentId: String    // 文档唯一标识</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="delete/admin/knowledge/{documentid}-3"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;文档已成功从知识库中删除&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="delete/admin/knowledge/{documentid}-4"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="string">&quot;删除文档失败: error message&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="📊-4-获取系统状态">📊 4. 获取系统状态</h2><h3 id="🔄-流程步骤-4">🔄 流程步骤</h3><ol><li>✅ <strong>验证管理员</strong>：验证当前用户的管理员身份</li><li>📈 <strong>收集数据</strong>：收集系统各项状态指标</li><li>📊 <strong>计算指标</strong>：计算CPU、内存、磁盘使用率等</li><li>📤 <strong>返回结果</strong>：返回系统状态信息</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="get/admin/system/status"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#get/admin/system/status-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#get/admin/system/status-2">成功响应</button></li><li class="tab"><button type="button" data-href="#get/admin/system/status-3">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="get/admin/system/status-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET /api/v1/admin/system/status</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/admin/system/status-2"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;cpu_usage&quot;</span><span class="punctuation">:</span> <span class="string">&quot;30%&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;memory_usage&quot;</span><span class="punctuation">:</span> <span class="string">&quot;45%&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;disk_usage&quot;</span><span class="punctuation">:</span> <span class="string">&quot;60%&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;active_users&quot;</span><span class="punctuation">:</span> <span class="number">15</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;total_documents&quot;</span><span class="punctuation">:</span> <span class="number">250</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;total_conversations&quot;</span><span class="punctuation">:</span> <span class="number">1200</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/admin/system/status-3"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="string">&quot;获取系统状态失败: error message&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="📋-5-获取用户活动日志">📋 5. 获取用户活动日志</h2><h3 id="🔄-流程步骤-5">🔄 流程步骤</h3><ol><li>✅ <strong>验证管理员</strong>：验证当前用户的管理员身份</li><li>🔍 <strong>参数解析</strong>：解析查询参数（用户名、时间范围等）</li><li>📊 <strong>查询日志</strong>：从数据库或缓存中获取用户活动数据</li><li>⏰ <strong>时间过滤</strong>：根据时间范围筛选活动记录</li><li>📤 <strong>返回结果</strong>：返回用户活动日志列表</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="get/admin/user-activities"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#get/admin/user-activities-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#get/admin/user-activities-2">请求参数</button></li><li class="tab"><button type="button" data-href="#get/admin/user-activities-3">成功响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="get/admin/user-activities-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET /api/v1/admin/user-activities</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/admin/user-activities-2"><p><strong>请求参数</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">username: String (可选)    // 指定用户名</span><br><span class="line">start_date: String (可选)  // 开始时间</span><br><span class="line">end_date: String (可选)    // 结束时间</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/admin/user-activities-3"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;user1&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;action&quot;</span><span class="punctuation">:</span> <span class="string">&quot;LOGIN&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;timestamp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2023-03-01T10:15:30&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;ip_address&quot;</span><span class="punctuation">:</span> <span class="string">&quot;192.168.1.100&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;user2&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;action&quot;</span><span class="punctuation">:</span> <span class="string">&quot;UPLOAD_FILE&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;timestamp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2023-03-01T11:20:45&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;ip_address&quot;</span><span class="punctuation">:</span> <span class="string">&quot;192.168.1.101&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="👤-6-创建管理员用户">👤 6. 创建管理员用户</h2><h3 id="🔄-流程步骤-6">🔄 流程步骤</h3><ol><li>✅ <strong>验证管理员</strong>：验证当前用户的管理员身份</li><li>📋 <strong>参数验证</strong>：验证新管理员用户名和密码</li><li>🔍 <strong>重复检查</strong>：检查用户名是否已存在</li><li>🔐 <strong>密码加密</strong>：使用BCrypt加密新用户密码</li><li>🎭 <strong>角色分配</strong>：为新用户分配ADMIN角色</li><li>🏷️ <strong>创建标签</strong>：创建私有组织标签</li><li>💾 <strong>保存用户</strong>：将新管理员信息保存到数据库</li><li>📤 <strong>返回结果</strong>：返回创建成功信息</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="post/admin/users/create-admin"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#post/admin/users/create-admin-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#post/admin/users/create-admin-2">请求体</button></li><li class="tab"><button type="button" data-href="#post/admin/users/create-admin-3">成功响应</button></li><li class="tab"><button type="button" data-href="#post/admin/users/create-admin-4">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="post/admin/users/create-admin-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">POST /api/v1/admin/users/create-admin</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/admin/users/create-admin-2"><p><strong>请求体</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;new_admin&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;password&quot;</span><span class="punctuation">:</span> <span class="string">&quot;secure_password&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/admin/users/create-admin-3"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;管理员用户创建成功&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/admin/users/create-admin-4"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">400</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;用户名已存在&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="🏷️-7-创建组织标签">🏷️ 7. 创建组织标签</h2><h3 id="🔄-流程步骤-7">🔄 流程步骤</h3><ol><li>✅ <strong>验证管理员</strong>：验证当前用户的管理员身份</li><li>📋 <strong>参数验证</strong>：验证标签ID、名称等必填参数</li><li>🔍 <strong>重复检查</strong>：检查标签ID是否已存在</li><li>👪 <strong>父标签验证</strong>：验证父标签是否存在（如果有）</li><li>💾 <strong>保存标签</strong>：将新标签信息保存到数据库</li><li>📤 <strong>返回结果</strong>：返回创建成功的标签信息</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="post/admin/org-tags"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#post/admin/org-tags-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#post/admin/org-tags-2">请求体</button></li><li class="tab"><button type="button" data-href="#post/admin/org-tags-3">成功响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="post/admin/org-tags-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">POST /api/v1/admin/org-tags</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/admin/org-tags-2"><p><strong>请求体</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;tagId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dept_research&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;研究部&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;研究部门组织标签&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;parentTag&quot;</span><span class="punctuation">:</span> <span class="string">&quot;company&quot;</span>  <span class="comment">// 可选</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/admin/org-tags-3"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;组织标签创建成功&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;tagId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dept_research&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;研究部&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;研究部门组织标签&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;parentTag&quot;</span><span class="punctuation">:</span> <span class="string">&quot;company&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="📋-8-获取所有组织标签">📋 8. 获取所有组织标签</h2><h3 id="🔄-流程步骤-8">🔄 流程步骤</h3><ol><li>✅ <strong>验证管理员</strong>：验证当前用户的管理员身份</li><li>📊 <strong>查询标签</strong>：从数据库获取所有组织标签</li><li>📤 <strong>返回结果</strong>：返回标签列表</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="get/admin/org-tags"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#get/admin/org-tags-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#get/admin/org-tags-2">成功响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="get/admin/org-tags-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET /api/v1/admin/org-tags</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/admin/org-tags-2"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;获取组织标签成功&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;tagId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dept_research&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;研究部&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;研究部门组织标签&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="🔗-9-为用户分配组织标签">🔗 9. 为用户分配组织标签</h2><h3 id="🔄-流程步骤-9">🔄 流程步骤</h3><ol><li>✅ <strong>验证管理员</strong>：验证当前用户的管理员身份</li><li>📋 <strong>参数验证</strong>：验证用户ID和标签列表</li><li>🔍 <strong>用户验证</strong>：验证目标用户是否存在</li><li>🏷️ <strong>标签验证</strong>：验证所有标签是否存在</li><li>🔗 <strong>分配标签</strong>：将标签分配给用户</li><li>📝 <strong>记录日志</strong>：记录标签分配操作</li><li>📤 <strong>返回结果</strong>：返回分配成功信息</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="put/admin/users/{userid}/org-tags"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#put/admin/users/{userid}/org-tags-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#put/admin/users/{userid}/org-tags-2">路径参数</button></li><li class="tab"><button type="button" data-href="#put/admin/users/{userid}/org-tags-3">请求体</button></li><li class="tab"><button type="button" data-href="#put/admin/users/{userid}/org-tags-4">成功响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="put/admin/users/{userid}/org-tags-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">PUT /api/v1/admin/users/&#123;userId&#125;/org-tags</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="put/admin/users/{userid}/org-tags-2"><p><strong>路径参数</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">userId: Long    // 用户ID</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="put/admin/users/{userid}/org-tags-3"><p><strong>请求体</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;orgTags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;dept_research&quot;</span><span class="punctuation">,</span> <span class="string">&quot;team_ai&quot;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="put/admin/users/{userid}/org-tags-4"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;组织标签分配成功&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="🌳-10-获取组织标签树结构">🌳 10. 获取组织标签树结构</h2><h3 id="🔄-流程步骤-10">🔄 流程步骤</h3><ol><li>✅ <strong>验证管理员</strong>：验证当前用户的管理员身份</li><li>📊 <strong>构建树形</strong>：从数据库获取标签并构建树形结构</li><li>🔄 <strong>层级处理</strong>：处理父子标签关系</li><li>📤 <strong>返回结果</strong>：返回树形结构的标签数据</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="get/admin/org-tags/tree"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#get/admin/org-tags/tree-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#get/admin/org-tags/tree-2">成功响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="get/admin/org-tags/tree-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET /api/v1/admin/org-tags/tree</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/admin/org-tags/tree-2"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;获取组织标签树成功&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;tagId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;company&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;公司&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;children&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;tagId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dept_research&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;研究部&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="✏️-11-更新组织标签">✏️ 11. 更新组织标签</h2><h3 id="🔄-流程步骤-11">🔄 流程步骤</h3><ol><li>✅ <strong>验证管理员</strong>：验证当前用户的管理员身份</li><li>📋 <strong>参数验证</strong>：验证更新参数</li><li>🔍 <strong>标签验证</strong>：验证目标标签是否存在</li><li>👪 <strong>父标签验证</strong>：验证新的父标签（如果有）</li><li>✏️ <strong>更新信息</strong>：更新标签的名称、描述等信息</li><li>📝 <strong>记录日志</strong>：记录标签更新操作</li><li>📤 <strong>返回结果</strong>：返回更新后的标签信息</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="put/admin/org-tags/{tagid}"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#put/admin/org-tags/{tagid}-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#put/admin/org-tags/{tagid}-2">路径参数</button></li><li class="tab"><button type="button" data-href="#put/admin/org-tags/{tagid}-3">请求体</button></li><li class="tab"><button type="button" data-href="#put/admin/org-tags/{tagid}-4">成功响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="put/admin/org-tags/{tagid}-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">PUT /api/v1/admin/org-tags/&#123;tagId&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="put/admin/org-tags/{tagid}-2"><p><strong>路径参数</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tagId: String    // 标签ID</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="put/admin/org-tags/{tagid}-3"><p><strong>请求体</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;更新后的名称&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;更新后的描述&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;parentTag&quot;</span><span class="punctuation">:</span> <span class="string">&quot;new_parent&quot;</span>  <span class="comment">// 可选</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="put/admin/org-tags/{tagid}-4"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;组织标签更新成功&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;tagId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dept_research&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;更新后的名称&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;更新后的描述&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="🗑️-12-删除组织标签">🗑️ 12. 删除组织标签</h2><h3 id="🔄-流程步骤-12">🔄 流程步骤</h3><ol><li>✅ <strong>验证管理员</strong>：验证当前用户的管理员身份</li><li>🔍 <strong>标签验证</strong>：验证目标标签是否存在</li><li>⚠️ <strong>依赖检查</strong>：检查是否有子标签或用户关联</li><li>🗑️ <strong>删除标签</strong>：从数据库中删除标签</li><li>📝 <strong>记录日志</strong>：记录标签删除操作</li><li>📤 <strong>返回结果</strong>：返回删除成功信息</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="delete/admin/org-tags/{tagid}"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#delete/admin/org-tags/{tagid}-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#delete/admin/org-tags/{tagid}-2">路径参数</button></li><li class="tab"><button type="button" data-href="#delete/admin/org-tags/{tagid}-3">成功响应</button></li><li class="tab"><button type="button" data-href="#delete/admin/org-tags/{tagid}-4">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="delete/admin/org-tags/{tagid}-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DELETE /api/v1/admin/org-tags/&#123;tagId&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="delete/admin/org-tags/{tagid}-2"><p><strong>路径参数</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tagId: String    // 要删除的标签ID</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="delete/admin/org-tags/{tagid}-3"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;组织标签删除成功&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="delete/admin/org-tags/{tagid}-4"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">400</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;该标签正在被使用，无法删除&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="📋-13-获取用户列表（分页）">📋 13. 获取用户列表（分页）</h2><h3 id="🔄-流程步骤-13">🔄 流程步骤</h3><ol><li>✅ <strong>验证管理员</strong>：验证当前用户的管理员身份</li><li>📋 <strong>参数解析</strong>：解析分页参数和筛选条件</li><li>🔍 <strong>条件查询</strong>：根据关键词、组织标签、状态等条件查询用户</li><li>📊 <strong>分页处理</strong>：执行分页查询</li><li>📤 <strong>返回结果</strong>：返回分页的用户数据</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="get/admin/users/list"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#get/admin/users/list-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#get/admin/users/list-2">请求参数</button></li><li class="tab"><button type="button" data-href="#get/admin/users/list-3">成功响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="get/admin/users/list-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET /api/v1/admin/users/list</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/admin/users/list-2"><p><strong>请求参数</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">keyword: String (可选)     // 搜索关键词</span><br><span class="line">orgTag: String (可选)      // 组织标签筛选</span><br><span class="line">status: Integer (可选)     // 用户状态筛选</span><br><span class="line">page: int (默认1)         // 页码</span><br><span class="line">size: int (默认20)        // 每页数量</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/admin/users/list-3"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;获取用户列表成功&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;user1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;role&quot;</span><span class="punctuation">:</span> <span class="string">&quot;USER&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;orgTags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;PRIVATE_user1&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;primaryOrg&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PRIVATE_user1&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;totalElements&quot;</span><span class="punctuation">:</span> <span class="number">100</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;totalPages&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;currentPage&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;pageSize&quot;</span><span class="punctuation">:</span> <span class="number">20</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="💬-14-管理员查询所有对话历史">💬 14. 管理员查询所有对话历史</h2><h3 id="🔄-流程步骤-14">🔄 流程步骤</h3><ol><li>✅ <strong>验证管理员</strong>：验证当前用户的管理员身份</li><li>📋 <strong>参数解析</strong>：解析查询参数（用户ID、时间范围等）</li><li>🔍 <strong>用户验证</strong>：如果指定用户ID，验证用户是否存在</li><li>📊 <strong>查询对话</strong>：从Redis中查询所有对话历史</li><li>⏰ <strong>时间过滤</strong>：根据时间范围筛选对话记录</li><li>📤 <strong>返回结果</strong>：返回格式化的对话历史数据</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="get/admin/conversation"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#get/admin/conversation-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#get/admin/conversation-2">请求参数</button></li><li class="tab"><button type="button" data-href="#get/admin/conversation-3">成功响应</button></li><li class="tab"><button type="button" data-href="#get/admin/conversation-4">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="get/admin/conversation-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET /api/v1/admin/conversation</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/admin/conversation-2"><p><strong>请求参数</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">userid: String (可选)      // 指定用户ID</span><br><span class="line">start_date: String (可选)  // 开始时间 (格式: 2023-01-01T12:00:00)</span><br><span class="line">end_date: String (可选)    // 结束时间 (格式: 2023-01-01T12:00:00)</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/admin/conversation-3"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;获取对话历史成功&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;role&quot;</span><span class="punctuation">:</span> <span class="string">&quot;user&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;用户消息内容&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;timestamp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2023-03-01T10:15:30&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;user1&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;role&quot;</span><span class="punctuation">:</span> <span class="string">&quot;assistant&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;助手回复内容&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;timestamp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2023-03-01T10:15:32&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;user1&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/admin/conversation-4"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">404</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;目标用户不存在&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h1>🗃️ 四、库表设计</h1><h2 id="👤-1-用户表">👤 1. 用户表</h2><blockquote><p>与用户管理模块共用，详见用户管理模块文档</p></blockquote><h2 id="🏷️-2-组织标签表">🏷️ 2. 组织标签表</h2><blockquote><p>与用户管理模块共用，详见用户管理模块文档</p></blockquote><h2 id="📊-3-系统日志表">📊 3. 系统日志表</h2><div class="tabs" id="system-log-table"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#system-log-table-1">字段设计</button></li><li class="tab"><button type="button" data-href="#system-log-table-2">建表语句</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="system-log-table-1"><p><strong>字段设计</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">| 🔑 <span class="strong">**字段**</span> | 📋 <span class="strong">**类型**</span> | 📝 <span class="strong">**描述**</span> |</span><br><span class="line">| --- | --- | --- |</span><br><span class="line">| <span class="code">`id`</span> | <span class="code">`BIGINT`</span> | 🆔 主键，日志ID |</span><br><span class="line">| <span class="code">`operation_type`</span> | <span class="code">`VARCHAR(50)`</span> | 📋 操作类型 |</span><br><span class="line">| <span class="code">`operator`</span> | <span class="code">`VARCHAR(255)`</span> | 👤 操作者用户名 |</span><br><span class="line">| <span class="code">`target_user`</span> | <span class="code">`VARCHAR(255)`</span> | 🎯 目标用户（可选） |</span><br><span class="line">| <span class="code">`details`</span> | <span class="code">`TEXT`</span> | 📝 操作详情 |</span><br><span class="line">| <span class="code">`ip_address`</span> | <span class="code">`VARCHAR(45)`</span> | 🌐 IP地址 |</span><br><span class="line">| <span class="code">`user_agent`</span> | <span class="code">`TEXT`</span> | 🧭 用户代理 |</span><br><span class="line">| <span class="code">`status`</span> | <span class="code">`ENUM(&#x27;SUCCESS&#x27;, &#x27;FAILURE&#x27;)`</span> | ✅ 操作状态 |</span><br><span class="line">| <span class="code">`error_message`</span> | <span class="code">`TEXT`</span> | ❌ 错误信息（失败时） |</span><br><span class="line">| <span class="code">`created_at`</span> | <span class="code">`TIMESTAMP`</span> | 📅 创建时间 |</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="system-log-table-2"><p><strong>建表语句</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> system_logs (</span><br><span class="line">    id <span class="type">BIGINT</span> AUTO_INCREMENT <span class="keyword">PRIMARY KEY</span> COMMENT <span class="string">&#x27;日志唯一标识&#x27;</span>,</span><br><span class="line">    operation_type <span class="type">VARCHAR</span>(<span class="number">50</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;操作类型&#x27;</span>,</span><br><span class="line">    operator <span class="type">VARCHAR</span>(<span class="number">255</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;操作者用户名&#x27;</span>,</span><br><span class="line">    target_user <span class="type">VARCHAR</span>(<span class="number">255</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;目标用户&#x27;</span>,</span><br><span class="line">    details TEXT COMMENT <span class="string">&#x27;操作详情&#x27;</span>,</span><br><span class="line">    ip_address <span class="type">VARCHAR</span>(<span class="number">45</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;IP地址&#x27;</span>,</span><br><span class="line">    user_agent TEXT COMMENT <span class="string">&#x27;用户代理&#x27;</span>,</span><br><span class="line">    status ENUM(<span class="string">&#x27;SUCCESS&#x27;</span>, <span class="string">&#x27;FAILURE&#x27;</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;操作状态&#x27;</span>,</span><br><span class="line">    error_message TEXT COMMENT <span class="string">&#x27;错误信息&#x27;</span>,</span><br><span class="line">    created_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;创建时间&#x27;</span>,</span><br><span class="line">    INDEX idx_operator (operator) COMMENT <span class="string">&#x27;操作者索引&#x27;</span>,</span><br><span class="line">    INDEX idx_created_at (created_at) COMMENT <span class="string">&#x27;时间索引&#x27;</span></span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 COMMENT<span class="operator">=</span><span class="string">&#x27;系统日志表&#x27;</span>;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="📚-4-知识库文档表">📚 4. 知识库文档表</h2><div class="tabs" id="knowledge-doc-table"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#knowledge-doc-table-1">字段设计</button></li><li class="tab"><button type="button" data-href="#knowledge-doc-table-2">建表语句</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="knowledge-doc-table-1"><p><strong>字段设计</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">| 🔑 <span class="strong">**字段**</span> | 📋 <span class="strong">**类型**</span> | 📝 <span class="strong">**描述**</span> |</span><br><span class="line">| --- | --- | --- |</span><br><span class="line">| <span class="code">`id`</span> | <span class="code">`BIGINT`</span> | 🆔 主键，文档ID |</span><br><span class="line">| <span class="code">`document_id`</span> | <span class="code">`VARCHAR(100)`</span> | 📄 文档唯一标识 |</span><br><span class="line">| <span class="code">`file_name`</span> | <span class="code">`VARCHAR(255)`</span> | 📎 文件名 |</span><br><span class="line">| <span class="code">`file_path`</span> | <span class="code">`TEXT`</span> | 📍 文件路径 |</span><br><span class="line">| <span class="code">`description`</span> | <span class="code">`TEXT`</span> | 📝 文档描述 |</span><br><span class="line">| <span class="code">`file_size`</span> | <span class="code">`BIGINT`</span> | 📏 文件大小 |</span><br><span class="line">| <span class="code">`mime_type`</span> | <span class="code">`VARCHAR(100)`</span> | 🎯 文件类型 |</span><br><span class="line">| <span class="code">`uploaded_by`</span> | <span class="code">`BIGINT`</span> | 👤 上传者ID |</span><br><span class="line">| <span class="code">`status`</span> | <span class="code">`ENUM(&#x27;ACTIVE&#x27;, &#x27;DELETED&#x27;)`</span> | 📊 文档状态 |</span><br><span class="line">| <span class="code">`created_at`</span> | <span class="code">`TIMESTAMP`</span> | 📅 创建时间 |</span><br><span class="line">| <span class="code">`updated_at`</span> | <span class="code">`TIMESTAMP`</span> | 🔄 更新时间 |</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="knowledge-doc-table-2"><p><strong>建表语句</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> knowledge_documents (</span><br><span class="line">    id <span class="type">BIGINT</span> AUTO_INCREMENT <span class="keyword">PRIMARY KEY</span> COMMENT <span class="string">&#x27;文档唯一标识&#x27;</span>,</span><br><span class="line">    document_id <span class="type">VARCHAR</span>(<span class="number">100</span>) <span class="keyword">NOT NULL</span> <span class="keyword">UNIQUE</span> COMMENT <span class="string">&#x27;文档唯一标识&#x27;</span>,</span><br><span class="line">    file_name <span class="type">VARCHAR</span>(<span class="number">255</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;文件名&#x27;</span>,</span><br><span class="line">    file_path TEXT <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;文件存储路径&#x27;</span>,</span><br><span class="line">    description TEXT COMMENT <span class="string">&#x27;文档描述&#x27;</span>,</span><br><span class="line">    file_size <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;文件大小&#x27;</span>,</span><br><span class="line">    mime_type <span class="type">VARCHAR</span>(<span class="number">100</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;文件MIME类型&#x27;</span>,</span><br><span class="line">    uploaded_by <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;上传者ID&#x27;</span>,</span><br><span class="line">    status ENUM(<span class="string">&#x27;ACTIVE&#x27;</span>, <span class="string">&#x27;DELETED&#x27;</span>) <span class="keyword">DEFAULT</span> <span class="string">&#x27;ACTIVE&#x27;</span> COMMENT <span class="string">&#x27;文档状态&#x27;</span>,</span><br><span class="line">    created_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;创建时间&#x27;</span>,</span><br><span class="line">    updated_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> <span class="keyword">ON</span> <span class="keyword">UPDATE</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;更新时间&#x27;</span>,</span><br><span class="line">    <span class="keyword">FOREIGN KEY</span> (uploaded_by) <span class="keyword">REFERENCES</span> users(id),</span><br><span class="line">    INDEX idx_document_id (document_id) COMMENT <span class="string">&#x27;文档ID索引&#x27;</span>,</span><br><span class="line">    INDEX idx_status (status) COMMENT <span class="string">&#x27;状态索引&#x27;</span>,</span><br><span class="line">    INDEX idx_created_at (created_at) COMMENT <span class="string">&#x27;时间索引&#x27;</span></span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 COMMENT<span class="operator">=</span><span class="string">&#x27;知识库文档表&#x27;</span>;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div>]]></content>
    
    
    <summary type="html">科研文献智能 RAG 知识库系统管理员模块详细设计文档</summary>
    
    
    
    <category term="学海拾贝" scheme="https://blog.adoreorg.cn/categories/%E5%AD%A6%E6%B5%B7%E6%8B%BE%E8%B4%9D/"/>
    
    
    <category term="管理员模块" scheme="https://blog.adoreorg.cn/tags/%E7%AE%A1%E7%90%86%E5%91%98%E6%A8%A1%E5%9D%97/"/>
    
    <category term="系统管理" scheme="https://blog.adoreorg.cn/tags/%E7%B3%BB%E7%BB%9F%E7%AE%A1%E7%90%86/"/>
    
    <category term="用户管理" scheme="https://blog.adoreorg.cn/tags/%E7%94%A8%E6%88%B7%E7%AE%A1%E7%90%86/"/>
    
    <category term="知识库管理" scheme="https://blog.adoreorg.cn/tags/%E7%9F%A5%E8%AF%86%E5%BA%93%E7%AE%A1%E7%90%86/"/>
    
    <category term="系统监控" scheme="https://blog.adoreorg.cn/tags/%E7%B3%BB%E7%BB%9F%E7%9B%91%E6%8E%A7/"/>
    
  </entry>
  
  <entry>
    <title>派洞察 - 用户管理模块</title>
    <link href="https://blog.adoreorg.cn/posts/9b55fe42.html"/>
    <id>https://blog.adoreorg.cn/posts/9b55fe42.html</id>
    <published>2025-07-04T16:00:00.000Z</published>
    <updated>2025-11-21T11:39:52.679Z</updated>
    
    <content type="html"><![CDATA[<h1>🎯 派洞察——科研文献智能 RAG 知识库系统</h1><h2 id="📋-用户管理模块">📋 用户管理模块</h2><blockquote><p><strong>模块定位</strong>：用户管理模块是系统的核心基础模块，负责处理用户的注册、登录和权限控制功能。</p></blockquote><h3 id="🎯-核心目标">🎯 核心目标</h3><ul><li>✅ <strong>身份安全</strong>：确保用户身份的安全性和认证可靠性</li><li>✅ <strong>权限管理</strong>：提供灵活的权限管理机制，支持基于角色的访问控制（RBAC）</li><li>✅ <strong>数据隔离</strong>：通过组织标签实现数据访问权限隔离</li><li>✅ <strong>服务支撑</strong>：为其他业务模块提供用户信息支持</li></ul><h1>📊 一、功能需求</h1><h2 id="👤-用户注册">👤 用户注册</h2><blockquote><p>📝 <strong>功能描述</strong>：允许新用户通过用户名和密码注册，注册时默认分配普通用户角色（USER）。</p><p>🔧 <strong>管理功能</strong>：管理员可在后台为新用户分派组织标签（如研究组A、研究组B等），以实现数据权限隔离。</p></blockquote><h2 id="🔐-用户登录">🔐 用户登录</h2><blockquote><p>🔑 <strong>认证方式</strong>：允许用户通过用户名和密码登录系统，登录成功后返回JWT token，后续请求在请求头中携带该token进行身份认证。</p><p>📋 <strong>Token内容</strong>：JWT Token包含用户基本信息和组织标签信息，用于无状态认证。</p></blockquote><h2 id="🛡️-用户权限控制（RBAC）">🛡️ 用户权限控制（RBAC）</h2><blockquote><p>🎯 <strong>权限模型</strong>：通过RBAC实现对不同角色（普通用户和管理员）的功能权限区分，普通用户只能访问自己有权限的数据，管理员可以访问所有数据。</p><p>🏷️ <strong>组织管理</strong>：管理员可以分配组织标签，如研究组A、研究组B等，以实现数据权限隔离。</p><p>🔒 <strong>访问控制</strong>：普通用户只能访问自己所属组织标签的数据，不能访问其他组织标签的数据。</p><p>🔧 <strong>安全配置</strong>：使用Spring Security配置基于角色的权限控制，确保只有授权角色的用户才能访问相应的接口。</p></blockquote><h2 id="🏷️-组织标签">🏷️ 组织标签</h2><blockquote><p>🆔 <strong>私有标签</strong>：用户注册时自动创建并分配个人私有组织标签（格式为：PRIVATE_username），并保存到数据库中。</p><p>📍 <strong>默认设置</strong>：私有组织标签设置为用户默认的组织标签。</p><p>🔗 <strong>绑定关系</strong>：私有组织标签与用户绑定，管理员无法移除。</p><p>🛡️ <strong>访问限制</strong>：私有组织标签的资源仅限于本人和管理员访问。</p></blockquote><h2 id="📁-数据权限">📁 数据权限</h2><blockquote><p>📋 <strong>权限规则</strong>：文件访问权限规则如下：</p><ul><li>🔒 <strong>私有组织标签资源</strong>：私有组织标签的资源仅限于本人和管理员访问</li><li>👥 <strong>组织标签资源</strong>：拥有该组织标签的用户可以访问该组织标签下的所有资源</li><li>🌍 <strong>默认组织标签资源</strong>：所有用户可访问</li><li>📢 <strong>公开标记资源</strong>：不受组织标签限制，所有用户均可访问</li></ul><p>📤 <strong>上传规则</strong>：文件上传时自动关联用户的主组织标签（默认为私有标签）。</p><p>⚙️ <strong>用户控制</strong>：用户可指定上传文件的组织标签和公开权限。</p></blockquote><h1>🛠️ 二、技术方案</h1><h2 id="🔐-身份认证与授权">🔐 身份认证与授权</h2><blockquote><p>🎯 <strong>JWT</strong>：用于无状态身份认证，由JWT生成token，token包含用户信息，服务端验证token，并获取用户信息。</p><p>🛡️ <strong>Spring Security</strong>：用于实现身份认证与授权。</p><p>🔒 <strong>BCryptPasswordEncoder</strong>：用于密码加密存储，确保用户密码的安全性。</p><p>🎨 <strong>自定义权限过滤器</strong>：实现基于组织标签的数据权限控制</p></blockquote><h2 id="💾-数据持久化">💾 数据持久化</h2><blockquote><p>🗄️ <strong>MySQL</strong>：用于存储用户信息，组织标签，数据权限信息。</p><p>⚡ <strong>Redis</strong>：用于缓存用户信息，组织标签，数据权限信息。</p><p>🚀 <strong>Spring Data JPA</strong>：用于简化数据库操作，提供CRUD操作。</p></blockquote><h2 id="🧰-辅助工具">🧰 辅助工具</h2><blockquote><p>🔤 <strong>Apache Commons Lang3</strong>：提供字符串处理和验证工具。</p><p>📝 <strong>Lombok</strong>：用于简化实体类的编写，通过注解自动生成构造函数、getter/setter方法等。</p><p>✅ <strong>Validation API</strong>：用于验证用户输入数据的合法性，如用户名唯一性、密码格式、密码长度等。</p></blockquote><h1>📝 三、关键流程</h1><h2 id="📋-1-用户注册">📋 1. 用户注册</h2><h3 id="🔄-流程步骤">🔄 流程步骤</h3><ol><li>📤 <strong>接收请求</strong>：客户端发送注册请求，包含用户名、密码、邮箱等信息</li><li>✅ <strong>数据验证</strong>：服务端验证用户输入数据的合法性（如用户名唯一性、密码强度、邮箱格式等）</li><li>🔐 <strong>密码加密</strong>：使用BCryptPasswordEncoder对密码进行加密</li><li>💾 <strong>数据存储</strong>：将用户信息保存到数据库中</li><li>🏷️ <strong>创建标签</strong>：自动创建用户的私有组织标签（PRIVATE_username）</li><li>🎭 <strong>分配角色</strong>：为用户分配默认角色（USER）</li><li>📤 <strong>返回结果</strong>：返回注册成功信息</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="post/register"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#post/register-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#post/register-2">请求体</button></li><li class="tab"><button type="button" data-href="#post/register-3">成功响应</button></li><li class="tab"><button type="button" data-href="#post/register-4">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="post/register-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">POST /api/v1/users/register</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/register-2"><p><strong>请求体（JSON）</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span><span class="punctuation">,</span>   <span class="comment">// 用户名，唯一</span></span><br><span class="line">  <span class="attr">&quot;password&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span>    <span class="comment">// 密码（明文传输，后端加密存储）</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/register-3"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;User registered successfully&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/register-4"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">400</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Username already exists&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="🔑-2-用户登录">🔑 2. 用户登录</h2><h3 id="🔄-流程步骤-2">🔄 流程步骤</h3><ol><li>📤 <strong>接收请求</strong>：客户端发送登录请求，包含用户名和密码</li><li>✅ <strong>用户验证</strong>：检查用户名是否存在</li><li>🔐 <strong>密码验证</strong>：使用BCrypt验证密码是否匹配</li><li>🎯 <strong>Token生成</strong>：生成JWT token，包含用户信息和组织标签<ul><li>🔑 <strong>访问token</strong>（access token）：用于访问受保护资源，有效期30分钟</li><li>🔄 <strong>刷新token</strong>（refresh token）：用于获取新访问token，有效期7天</li></ul></li><li>📤 <strong>返回结果</strong>：返回token给客户端</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="post/login"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#post/login-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#post/login-2">请求体</button></li><li class="tab"><button type="button" data-href="#post/login-3">成功响应</button></li><li class="tab"><button type="button" data-href="#post/login-4">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="post/login-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">POST /api/v1/users/login</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/login-2"><p><strong>请求体（JSON）</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span><span class="punctuation">,</span>   <span class="comment">// 用户名</span></span><br><span class="line">  <span class="attr">&quot;password&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span>    <span class="comment">// 密码（明文传输，后端加密存储）</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/login-3"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Login successful&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;token&quot;</span><span class="punctuation">:</span> <span class="string">&quot;JWT_TOKEN_STRING&quot;</span>       <span class="comment">// JWT token</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/login-4"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">401</span><span class="punctuation">,</span> <span class="comment">// 失败</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Invalid username or password&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="👤-3-获取当前用户信息">👤 3. 获取当前用户信息</h2><h3 id="🔄-流程步骤-3">🔄 流程步骤</h3><ol><li>📤 <strong>接收请求</strong>：客户端发送请求，请求头中携带JWT token</li><li>🔍 <strong>提取Token</strong>：服务端从请求头中提取token</li><li>✅ <strong>验证Token</strong>：验证token的有效性（签名、过期时间等）</li><li>📋 <strong>解析信息</strong>：从token中解析出username</li><li>🔍 <strong>查询详情</strong>：根据用户ID查询用户详细信息</li><li>📤 <strong>返回结果</strong>：返回用户信息</li></ol><h3 id="🔌-接口设计">🔌 接口设计</h3><div class="tabs" id="get/user"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#get/user-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#get/user-2">成功响应</button></li><li class="tab"><button type="button" data-href="#get/user-3">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="get/user-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET /api/v1/users/me</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/user-2"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Success&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;example_user&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;role&quot;</span><span class="punctuation">:</span> <span class="string">&quot;USER&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;orgTags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;PRIVATE_example_user&quot;</span><span class="punctuation">,</span> <span class="string">&quot;dept1&quot;</span><span class="punctuation">,</span> <span class="string">&quot;team2&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;primaryOrg&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PRIVATE_example_user&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/user-3"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">401</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Unauthorized&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="4-获取用户组织标签">4. 获取用户组织标签</h2><p><strong>流程</strong></p><ol><li>从 Authorization 中取出 Bearer token</li><li>使用 JwtUtils 提取 username</li><li>调用 userService.getUserOrgTags(username)</li><li>返回组织标签列表与主组织标签</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="get/org-tags"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#get/org-tags-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#get/org-tags-2">成功响应</button></li><li class="tab"><button type="button" data-href="#get/org-tags-3">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="get/org-tags-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET /api/v1/users/org-tags</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/org-tags-2"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Get user organization tags successful&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;orgTags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;PRIVATE_example_user&quot;</span><span class="punctuation">,</span> <span class="string">&quot;dept1&quot;</span><span class="punctuation">,</span> <span class="string">&quot;team2&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;primaryOrg&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PRIVATE_example_user&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;orgTagDetails&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;tagId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PRIVATE_example_user&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;example_user的私人空间&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;用户的私人组织标签，仅用户本人可访问&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;tagId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dept1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;部门1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;部门1的组织标签&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;tagId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;team2&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;团队2&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;团队2的组织标签&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="get/org-tags-3"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">401</span><span class="punctuation">,</span> <span class="comment">// 失败</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Unauthorized&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="5-设置主组织标签">5. 设置主组织标签</h2><p><strong>流程</strong></p><ol><li>从 Authorization 中取出 Bearer token</li><li>使用 JwtUtils 提取 username</li><li>校验参数 primaryOrg 是否存在于 orgTags 中</li><li>调用 userService.setPrimaryOrg(username, primaryOrg)，对用户主组织属性进行保存</li><li>返回设置成功响应</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="put/primary-org"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#put/primary-org-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#put/primary-org-2">请求体</button></li><li class="tab"><button type="button" data-href="#put/primary-org-3">成功响应</button></li><li class="tab"><button type="button" data-href="#put/primary-org-4">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="put/primary-org-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">PUT /api/v1/users/primary-org</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="put/primary-org-2"><p><strong>请求体（JSON）</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;primaryOrg&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span> <span class="comment">// 主组织标签</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="put/primary-org-3"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Primary organization set successfully&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="put/primary-org-4"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">401</span><span class="punctuation">,</span> <span class="comment">// 失败</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Unauthorized&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="📤-6-上传文件时的组织标签">📤 6. 上传文件时的组织标签</h2><h3 id="🔄-流程步骤-4">🔄 流程步骤</h3><ol><li>🆔 <strong>获取用户ID</strong>：使用 @RequestAttribute(“userId”)（由拦截器提前解析）获取当前用户 ID</li><li>📋 <strong>返回信息</strong>：返回用户全部 orgTags 与 primaryOrg 字段</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="post/upload-orgs"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#post/upload-orgs-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#post/upload-orgs-2">请求参数</button></li><li class="tab"><button type="button" data-href="#post/upload-orgs-3">成功响应</button></li><li class="tab"><button type="button" data-href="#post/upload-orgs-4">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="post/upload-orgs-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GET /api/v1/users/upload-orgs</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/upload-orgs-2"><p><strong>请求参数</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">userId<span class="punctuation">:</span> string</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/upload-orgs-3"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;获取用户上传组织标签成功&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;orgTags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;tag1&quot;</span><span class="punctuation">,</span> <span class="string">&quot;tag2&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;primaryOrg&quot;</span><span class="punctuation">:</span> <span class="string">&quot;tagMain&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/upload-orgs-4"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">401</span><span class="punctuation">,</span> <span class="comment">// 失败</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Unauthorized&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="🚪-7-用户登出">🚪 7. 用户登出</h2><h3 id="🔄-流程步骤-5">🔄 流程步骤</h3><ol><li>🔍 <strong>提取Token</strong>：从 Authorization 中取出 Bearer token</li><li>👤 <strong>解析用户</strong>：使用 JwtUtils 提取 username</li><li>❌ <strong>Token失效</strong>：调用 jwtUtils.invalidateToken() 使 token 失效</li><li>📤 <strong>返回结果</strong>：返回登出成功响应</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="post/logout"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#post/logout-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#post/logout-2">成功响应</button></li><li class="tab"><button type="button" data-href="#post/logout-3">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="post/logout-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">POST /api/v1/users/logout</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/logout-2"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Logout successful&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/logout-3"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">400</span><span class="punctuation">,</span> <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Invalid token format&quot;</span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="🔒-8-用户全部设备登出">🔒 8. 用户全部设备登出</h2><h3 id="🔄-流程步骤-6">🔄 流程步骤</h3><ol><li>🔍 <strong>提取Token</strong>：从 Authorization 中取出 Bearer token</li><li>👤 <strong>解析信息</strong>：使用 JwtUtils 解析 token 获取 username 和 userId</li><li>❌ <strong>全部失效</strong>：调用 jwtUtils.invalidateAllUserTokens(userId) 使所有 token 失效</li><li>📤 <strong>返回结果</strong>：返回登出成功响应</li></ol><p><strong>接口设计</strong></p><div class="tabs" id="post/logout-all"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#post/logout-all-1">请求方式与路径</button></li><li class="tab"><button type="button" data-href="#post/logout-all-2">成功响应</button></li><li class="tab"><button type="button" data-href="#post/logout-all-3">失败响应</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="post/logout-all-1"><p><strong>请求URL</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">POST /api/v1/users/logout-all</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/logout-all-2"><p><strong>成功响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">200</span><span class="punctuation">,</span> <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Logout from all devices successful&quot;</span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="post/logout-all-3"><p><strong>失败响应</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># token 格式无效：</span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">400</span><span class="punctuation">,</span> <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Invalid token format&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"># token 无法解析用户名或 userId：</span><br><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">401</span><span class="punctuation">,</span> <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Invalid token&quot;</span> <span class="punctuation">&#125;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h1>🗃️ 四、库表设计</h1><h2 id="👤-1-用户表">👤 1. 用户表</h2><div class="tabs" id="user-table"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#user-table-1">字段设计</button></li><li class="tab"><button type="button" data-href="#user-table-2">建表语句</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="user-table-1"><p><strong>字段设计</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">| 🔑 字段 | 📋 类型 | 📝 描述 |</span><br><span class="line">| --- | --- | --- |</span><br><span class="line">| <span class="code">`id`</span> | <span class="code">`BIGINT`</span> | 🆔 主键，用户 ID |</span><br><span class="line">| <span class="code">`username`</span> | <span class="code">`varchar(255)`</span> | 👤 用户名，唯一 |</span><br><span class="line">| <span class="code">`password`</span> | <span class="code">`varchar(255)`</span> | 🔒 密码，加密存储 |</span><br><span class="line">| <span class="code">`role`</span> | <span class="code">`ENUM(&#x27;USER&#x27;, &#x27;ADMIN&#x27;)`</span> | 🎭 用户角色，如 USER、ADMIN 等 |</span><br><span class="line">| <span class="code">`org_tags`</span> | <span class="code">`varchar(255)`</span> | 🏷️ 用户所属组织标签，多个用逗号分隔 |</span><br><span class="line">| <span class="code">`primary_org`</span> | <span class="code">`varchar(50)`</span> | 📍 用户主组织标签 |</span><br><span class="line">| <span class="code">`created_at`</span> | <span class="code">`timestamp`</span> | 📅 创建时间 |</span><br><span class="line">| <span class="code">`updated_at`</span> | <span class="code">`timestamp`</span> | 🔄 更新时间 |</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="user-table-2"><p><strong>建表语句</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> users (</span><br><span class="line">    id <span class="type">BIGINT</span> AUTO_INCREMENT <span class="keyword">PRIMARY KEY</span> COMMENT <span class="string">&#x27;用户唯一标识&#x27;</span>,</span><br><span class="line">    username <span class="type">VARCHAR</span>(<span class="number">255</span>) <span class="keyword">NOT NULL</span> <span class="keyword">UNIQUE</span> COMMENT <span class="string">&#x27;用户名，唯一&#x27;</span>,</span><br><span class="line">    password <span class="type">VARCHAR</span>(<span class="number">255</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;加密后的密码&#x27;</span>,</span><br><span class="line">    role ENUM(<span class="string">&#x27;USER&#x27;</span>, <span class="string">&#x27;ADMIN&#x27;</span>) <span class="keyword">NOT NULL</span> <span class="keyword">DEFAULT</span> <span class="string">&#x27;USER&#x27;</span> COMMENT <span class="string">&#x27;用户角色&#x27;</span>,</span><br><span class="line">    org_tags <span class="type">VARCHAR</span>(<span class="number">255</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;用户所属组织标签，多个用逗号分隔&#x27;</span>,</span><br><span class="line">    primary_org <span class="type">VARCHAR</span>(<span class="number">50</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;用户主组织标签&#x27;</span>,</span><br><span class="line">    created_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;创建时间&#x27;</span>,</span><br><span class="line">    updated_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> <span class="keyword">ON</span> <span class="keyword">UPDATE</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;更新时间&#x27;</span>,</span><br><span class="line">    INDEX idx_username (username) COMMENT <span class="string">&#x27;用户名索引&#x27;</span></span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 COMMENT<span class="operator">=</span><span class="string">&#x27;用户表&#x27;</span>;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div><h2 id="🏷️-2-组织标签表">🏷️ 2. 组织标签表</h2><div class="tabs" id="org-tag-table"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#org-tag-table-1">字段设计</button></li><li class="tab"><button type="button" data-href="#org-tag-table-2">建表语句</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="org-tag-table-1"><p><strong>字段设计</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">| 🔑 字段 | 📋 类型 | 📝 描述 |</span><br><span class="line">| --- | --- | --- |</span><br><span class="line">| <span class="code">`tag_id`</span> | <span class="code">`VARCHAR(50)`</span> | 🆔 主键，组织标签 ID |</span><br><span class="line">| <span class="code">`name`</span> | <span class="code">`VARCHAR(100)`</span> | 🏷️ 组织标签名称 |</span><br><span class="line">| <span class="code">`description`</span> | <span class="code">`TEXT`</span> | 📝 组织标签描述 |</span><br><span class="line">| <span class="code">`parent_tag`</span> | <span class="code">`VARCHAR(50)`</span> | 👪 父标签ID |</span><br><span class="line">| <span class="code">`created_by`</span> | <span class="code">`BIGINT`</span> | 👤 创建者ID |</span><br><span class="line">| <span class="code">`created_at`</span> | <span class="code">`TIMESTAMP`</span> | 📅 创建时间 |</span><br><span class="line">| <span class="code">`updated_at`</span> | <span class="code">`TIMESTAMP`</span> | 🔄 更新时间 |</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="org-tag-table-2"><p><strong>建表语句</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> organization_tags (</span><br><span class="line">    tag_id <span class="type">VARCHAR</span>(<span class="number">50</span>) <span class="keyword">PRIMARY KEY</span> COMMENT <span class="string">&#x27;标签唯一标识&#x27;</span>,</span><br><span class="line">    name <span class="type">VARCHAR</span>(<span class="number">100</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;标签名称&#x27;</span>,</span><br><span class="line">    description TEXT COMMENT <span class="string">&#x27;描述&#x27;</span>,</span><br><span class="line">    parent_tag <span class="type">VARCHAR</span>(<span class="number">50</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;父标签ID&#x27;</span>,</span><br><span class="line">    created_by <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;创建者ID&#x27;</span>,</span><br><span class="line">    created_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;创建时间&#x27;</span>,</span><br><span class="line">    updated_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span> <span class="keyword">ON</span> <span class="keyword">UPDATE</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;更新时间&#x27;</span>,</span><br><span class="line">    <span class="keyword">FOREIGN KEY</span> (parent_tag) <span class="keyword">REFERENCES</span> organization_tags(tag_id) <span class="keyword">ON</span> <span class="keyword">DELETE</span> <span class="keyword">SET</span> <span class="keyword">NULL</span>,</span><br><span class="line">    <span class="keyword">FOREIGN KEY</span> (created_by) <span class="keyword">REFERENCES</span> users(id)</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 COMMENT<span class="operator">=</span><span class="string">&#x27;组织标签表&#x27;</span>;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div>]]></content>
    
    
    <summary type="html">科研文献智能 RAG 知识库系统用户管理模块详细设计文档</summary>
    
    
    
    <category term="学海拾贝" scheme="https://blog.adoreorg.cn/categories/%E5%AD%A6%E6%B5%B7%E6%8B%BE%E8%B4%9D/"/>
    
    
    <category term="组织标签" scheme="https://blog.adoreorg.cn/tags/%E7%BB%84%E7%BB%87%E6%A0%87%E7%AD%BE/"/>
    
    <category term="用户管理" scheme="https://blog.adoreorg.cn/tags/%E7%94%A8%E6%88%B7%E7%AE%A1%E7%90%86/"/>
    
    <category term="JWT认证" scheme="https://blog.adoreorg.cn/tags/JWT%E8%AE%A4%E8%AF%81/"/>
    
    <category term="RBAC权限" scheme="https://blog.adoreorg.cn/tags/RBAC%E6%9D%83%E9%99%90/"/>
    
    <category term="Spring Security" scheme="https://blog.adoreorg.cn/tags/Spring-Security/"/>
    
  </entry>
  
  <entry>
    <title>回溯算法详解：从入门到精通</title>
    <link href="https://blog.adoreorg.cn/posts/3n43l21g.html"/>
    <id>https://blog.adoreorg.cn/posts/3n43l21g.html</id>
    <published>2025-01-14T16:00:00.000Z</published>
    <updated>2025-10-07T15:41:58.253Z</updated>
    
    <content type="html"><![CDATA[<h1>🦋 回溯算法详解：从入门到精通</h1><blockquote><p>✨ <strong>一文掌握回溯算法精髓</strong>：系统性搜索 + 智能剪枝 = 高效解题</p><p>🎯 <strong>核心思想</strong>：试错法 + 系统性搜索 + 剪枝优化</p><p>📚 <strong>适用场景</strong>：组合、排列、子集、棋盘、字符串匹配等问题</p></blockquote><h2 id="📖-目录">📖 目录</h2><ul><li><a href="#%E4%BB%80%E4%B9%88%E6%98%AF%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95">什么是回溯算法</a></li><li><a href="#%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%A1%86%E6%9E%B6">回溯算法的基本框架</a></li><li><a href="#%E7%BB%8F%E5%85%B8%E9%97%AE%E9%A2%98%E8%AF%A6%E8%A7%A3">经典问题详解</a></li><li><a href="#%E5%89%AA%E6%9E%9D%E4%BC%98%E5%8C%96%E6%8A%80%E5%B7%A7">剪枝优化技巧</a></li><li><a href="#%E5%AE%9E%E9%99%85%E5%BA%94%E7%94%A8%E6%A1%88%E4%BE%8B">实际应用案例</a></li><li><a href="#%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90">性能分析</a></li><li><a href="#%E5%AE%9E%E6%88%98%E7%BB%83%E4%B9%A0">实战练习</a></li><li><a href="#%E7%9B%B8%E5%85%B3leetcode%E9%A2%98%E7%9B%AE">相关LeetCode题目</a></li></ul><hr><h2 id="🎯-什么是回溯算法">🎯 什么是回溯算法</h2><p>回溯算法（Backtracking）是一种系统地搜索问题解的算法。它通过尝试所有可能的候选解来找出所有解，当发现当前候选解不可能是正确的解时，就放弃该候选解并回溯到上一步，尝试其他可能性。</p><h3 id="核心思想">核心思想</h3><ul><li><strong>试错</strong>：尝试一种选择，如果不行就回退</li><li><strong>系统性</strong>：按照某种顺序系统地搜索所有可能解</li><li><strong>剪枝</strong>：提前排除不可能的分支，减少搜索空间</li></ul><h3 id="适用场景">适用场景</h3><ul><li>组合问题：从N个数中找出满足条件的组合</li><li>排列问题：N个数按一定规则全排列</li><li>子集问题：N个数的满足条件的子集</li><li>棋盘问题：N皇后、数独等</li><li>字符串匹配：通配符匹配、正则表达式</li></ul><hr><h2 id="🚀-快速入门指南">🚀 快速入门指南</h2><h3 id="🎯-什么是回溯算法？">🎯 什么是回溯算法？</h3><p>回溯算法 = <strong>深度优先搜索(DFS) + 状态撤销</strong></p><p>想象你在走迷宫：</p><ul><li>🚶‍♂️ <strong>向前走</strong>：选择一个方向继续前进</li><li>🚫 <strong>遇到死胡同</strong>：回退到上一个路口</li><li>🔄 <strong>尝试其他方向</strong>：选择另一个未尝试的方向</li><li>✅ <strong>找到出口</strong>：记录成功路径</li></ul><h3 id="💡-核心三要素">💡 核心三要素</h3><table><thead><tr><th>要素</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td><strong>路径</strong></td><td>已经做出的选择</td><td><code>path = [1, 3, 5]</code></td></tr><tr><td><strong>选择列表</strong></td><td>当前可以做的选择</td><td><code>candidates = [2, 4, 6, 8]</code></td></tr><tr><td><strong>结束条件</strong></td><td>何时停止搜索</td><td><code>sum &gt;= target</code></td></tr></tbody></table><h3 id="🔑-万能模板（记住这个就够了！）">🔑 万能模板（记住这个就够了！）</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(路径, 选择列表)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (满足结束条件) &#123;</span><br><span class="line">        结果.add(路径);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (选择 : 选择列表) &#123;</span><br><span class="line">        做选择(选择);           <span class="comment">// ✅ 第一步</span></span><br><span class="line">        backtrack(新路径, 新选择列表); <span class="comment">// 🚀 第二步  </span></span><br><span class="line">        回溯，撤销选择(选择);         <span class="comment">// ↩️ 第三步</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="🎨-举个简单例子：子集问题">🎨 举个简单例子：子集问题</h3><p>求集合 <code>[1, 2, 3]</code> 的所有子集：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 子集问题回溯算法核心实现</span></span><br><span class="line"><span class="comment"> * 📝 问题描述：对于每个元素，选还是不选？</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> nums 输入数组（不包含重复元素）</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> index 当前处理的元素索引，用于避免重复使用元素</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> result 存储所有子集结果的集合</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> path 当前正在构建的路径（子集）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> index, List&lt;List&lt;Integer&gt;&gt; result, List&lt;Integer&gt; path)</span> &#123;</span><br><span class="line">    <span class="comment">// 📝 记录当前路径（无论是否完整，都是有效子集）</span></span><br><span class="line">    result.add(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(path));</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 🔍 遍历剩余选择：从index开始避免重复使用元素</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> index; i &lt; nums.length; i++) &#123;</span><br><span class="line">        <span class="comment">// ✅ 做选择：将当前元素加入路径</span></span><br><span class="line">        path.add(nums[i]);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 递归探索：处理下一个元素（i+1确保不重复使用）</span></span><br><span class="line">        backtrack(nums, i + <span class="number">1</span>, result, path);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ↩️ 撤销选择：回退到不选当前元素的状态</span></span><br><span class="line">        path.remove(path.size() - <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 完整的子集问题解决方案</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> nums 输入数组</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 所有可能的子集</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> List&lt;List&lt;Integer&gt;&gt; <span class="title function_">subsets</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="comment">// 🛡️ 参数校验</span></span><br><span class="line">    <span class="keyword">if</span> (nums == <span class="literal">null</span> || nums.length == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    List&lt;List&lt;Integer&gt;&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    List&lt;Integer&gt; path = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 🚀 启动回溯算法</span></span><br><span class="line">    backtrack(nums, <span class="number">0</span>, result, path);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="⚡-一分钟理解回溯">⚡ 一分钟理解回溯</h3><ol><li><strong>做选择</strong>：在当前路口选择一个方向 ➡️</li><li><strong>探索到底</strong>：沿着这个方向一直走下去 🏃‍♂️</li><li><strong>回退撤销</strong>：遇到死胡同时回退 🔄</li><li><strong>尝试新方向</strong>：选择另一个未尝试的方向 🔄</li></ol><p><strong>关键</strong>：<strong>撤销选择</strong>让你能够系统地探索所有可能性！</p><hr><h2 id="🏗️-回溯算法的基本框架">🏗️ 回溯算法的基本框架</h2><h3 id="通用模板">通用模板</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BacktrackingTemplate</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(路径, 选择列表, 结果)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 终止条件</span></span><br><span class="line">        <span class="keyword">if</span> (满足结束条件) &#123;</span><br><span class="line">            结果.add(路径);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 遍历所有选择</span></span><br><span class="line">        <span class="keyword">for</span> (选择 : 选择列表) &#123;</span><br><span class="line">            <span class="comment">// 3. 做选择</span></span><br><span class="line">            路径.add(选择);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 4. 递归探索</span></span><br><span class="line">            backtrack(路径, 新的选择列表, 结果);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 5. 撤销选择（回溯）</span></span><br><span class="line">            路径.remove(选择);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="框架解析">框架解析</h3><ol><li><strong>路径</strong>：已经做出的选择</li><li><strong>选择列表</strong>：当前可以做的选择</li><li><strong>终止条件</strong>：到达决策树底层，无法再做选择的条件</li><li><strong>回溯</strong>：撤销当前选择，回到上一步状态</li></ol><hr><h2 id="🎯-解题四步法：系统攻克回溯问题">🎯 解题四步法：系统攻克回溯问题</h2><h3 id="📝-步骤1：问题分析">📝 步骤1：问题分析</h3><p><strong>🔍 关键问题</strong>：</p><ul><li>❓ <strong>选择是什么？</strong> 每个决策点有哪些选项？</li><li>❓ <strong>约束条件？</strong> 什么情况下不能继续？</li><li>❓ <strong>目标状态？</strong> 什么情况下找到解？</li></ul><p><strong>🎨 示例：组合总和问题</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">选择：从candidates中选一个数字</span><br><span class="line">约束：sum &lt;= target（剪枝），数字可重复使用</span><br><span class="line">目标：sum == target</span><br></pre></td></tr></table></figure><h3 id="🛠️-步骤2：设计回溯函数">🛠️ 步骤2：设计回溯函数</h3><p><strong>🎯 函数签名</strong>：<code>backtrack(路径, 选择列表, 其他参数)</code></p><p><strong>🧩 核心结构</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(路径, 选择列表, 约束参数)</span> &#123;</span><br><span class="line">    <span class="comment">// 🛑 结束条件：找到解或无法继续</span></span><br><span class="line">    <span class="keyword">if</span> (满足结束条件) &#123;</span><br><span class="line">        记录结果();</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 🔍 遍历所有选择</span></span><br><span class="line">    <span class="keyword">for</span> (选择 : 选择列表) &#123;</span><br><span class="line">        <span class="comment">// ❌ 剪枝：跳过无效选择</span></span><br><span class="line">        <span class="keyword">if</span> (!isValid(选择)) <span class="keyword">continue</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ✅ 做选择</span></span><br><span class="line">        路径.add(选择);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 递归探索</span></span><br><span class="line">        backtrack(新路径, 新选择列表, 新约束);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ↩️ 撤销选择</span></span><br><span class="line">        路径.remove(选择);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="⚡-步骤3：剪枝优化">⚡ 步骤3：剪枝优化</h3><p><strong>🔪 剪枝策略</strong>：</p><ul><li><strong>约束剪枝</strong>：违反约束直接返回</li><li><strong>目标剪枝</strong>：无法达到目标直接返回</li><li><strong>重复剪枝</strong>：避免重复计算相同状态</li></ul><p><strong>💡 剪枝口诀</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">🎯 能剪就剪，绝不手软</span><br><span class="line">🔍 提前判断，避免深搜</span><br><span class="line">⚡ 减少分支，提升效率</span><br></pre></td></tr></table></figure><h3 id="🧪-步骤4：测试验证">🧪 步骤4：测试验证</h3><p><strong>✅ 测试用例设计</strong>：</p><ul><li><strong>边界情况</strong>：空输入、单元素、最大值</li><li><strong>特殊情况</strong>：重复元素、无法解、多解</li><li><strong>性能测试</strong>：大数据量、时间复杂度</li></ul><p><strong>🎯 验证要点</strong>：</p><ul><li>结果是否正确？</li><li>是否去重？</li><li>是否剪枝？</li><li>时间复杂度？</li></ul><hr><h2 id="🧩-经典问题详解：从理论到实战">🧩 经典问题详解：从理论到实战</h2><blockquote><p>🎯 <strong>学习目标</strong>：通过4个经典问题，掌握回溯算法的核心技巧</p><p>📊 <strong>难度梯度</strong>：⭐☆☆☆ → ⭐⭐☆☆ → ⭐⭐⭐☆ → ⭐⭐⭐⭐</p><p>💡 <strong>核心技巧</strong>：剪枝优化 + 去重处理 + 约束条件</p></blockquote><h3 id="1-组合总和问题-⭐☆☆☆">1. 组合总和问题 ⭐☆☆☆</h3><p><strong>🎯 LeetCode 39</strong>：给定一个无重复元素的整数数组和一个目标和，找出所有可以使数字和为目标的组合。</p><p><strong>💡 核心思路</strong>：每个数字可以重复使用，通过剪枝避免无效搜索</p><p><strong>🎨 可视化</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">candidates = [2, 3, 6, 7], target = 7</span><br><span class="line"></span><br><span class="line">🔍 搜索过程：</span><br><span class="line">2 → 2 → 2 → 2 (sum=8 &gt; 7) ❌ 剪枝</span><br><span class="line">2 → 2 → 3 (sum=7 == 7) ✅ 找到解</span><br><span class="line">2 → 2 → 6 (sum=10 &gt; 7) ❌ 剪枝</span><br><span class="line">...</span><br><span class="line">7 (sum=7 == 7) ✅ 找到解</span><br><span class="line"></span><br><span class="line">✨ 结果：[[2, 2, 3], [7]]</span><br></pre></td></tr></table></figure><h4 id="原始实现">原始实现</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CombinationSum</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> List&lt;List&lt;Integer&gt;&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">private</span> List&lt;Integer&gt; path = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;List&lt;Integer&gt;&gt; <span class="title function_">combinationSum</span><span class="params">(<span class="type">int</span>[] candidates, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">        Arrays.sort(candidates);  <span class="comment">// 排序有助于剪枝</span></span><br><span class="line">        backtrack(candidates, target, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span>[] candidates, <span class="type">int</span> target, <span class="type">int</span> sum, <span class="type">int</span> startIndex)</span> &#123;</span><br><span class="line">        <span class="comment">// 终止条件：找到目标和</span></span><br><span class="line">        <span class="keyword">if</span> (sum == target) &#123;</span><br><span class="line">            result.add(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(path));</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 终止条件：超过目标和</span></span><br><span class="line">        <span class="keyword">if</span> (sum &gt; target) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 遍历选择列表</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> startIndex; i &lt; candidates.length; i++) &#123;</span><br><span class="line">            <span class="comment">// 做选择</span></span><br><span class="line">            path.add(candidates[i]);</span><br><span class="line">            sum += candidates[i];</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 递归探索：可以重复使用当前元素，所以i不变</span></span><br><span class="line">            backtrack(candidates, target, sum, i);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 撤销选择</span></span><br><span class="line">            path.remove(path.size() - <span class="number">1</span>);</span><br><span class="line">            sum -= candidates[i];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="优化实现：剪枝优化">优化实现：剪枝优化</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CombinationSumOptimized</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> List&lt;List&lt;Integer&gt;&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">private</span> List&lt;Integer&gt; path = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;List&lt;Integer&gt;&gt; <span class="title function_">combinationSum</span><span class="params">(<span class="type">int</span>[] candidates, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">        Arrays.sort(candidates);</span><br><span class="line">        backtrack(candidates, target, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span>[] candidates, <span class="type">int</span> target, <span class="type">int</span> sum, <span class="type">int</span> startIndex)</span> &#123;</span><br><span class="line">        <span class="comment">// 找到解</span></span><br><span class="line">        <span class="keyword">if</span> (sum == target) &#123;</span><br><span class="line">            result.add(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(path));</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 剪枝：提前终止不可能的分支</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> startIndex; i &lt; candidates.length; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (sum + candidates[i] &gt; target) &#123;</span><br><span class="line">                <span class="keyword">break</span>;  <span class="comment">// 排序后，后续元素更大，直接剪枝</span></span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            path.add(candidates[i]);</span><br><span class="line">            backtrack(candidates, target, sum + candidates[i], i);</span><br><span class="line">            path.remove(path.size() - <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-子集问题-⭐⭐☆☆">2. 子集问题 ⭐⭐☆☆</h3><p><strong>LeetCode 78</strong>：给定一组不含重复元素的整数数组，返回其所有可能的子集（幂集）。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Subsets</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> List&lt;List&lt;Integer&gt;&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">private</span> List&lt;Integer&gt; path = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;List&lt;Integer&gt;&gt; <span class="title function_">subsets</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">        backtrack(nums, <span class="number">0</span>);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> startIndex)</span> &#123;</span><br><span class="line">        <span class="comment">// 记录当前路径（无论是否完整，都是有效子集）</span></span><br><span class="line">        result.add(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(path));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 从startIndex开始避免重复使用元素</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> startIndex; i &lt; nums.length; i++) &#123;</span><br><span class="line">            <span class="comment">// 做选择：将当前元素加入路径</span></span><br><span class="line">            path.add(nums[i]);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 递归探索：处理下一个元素（i+1确保不重复使用）</span></span><br><span class="line">            backtrack(nums, i + <span class="number">1</span>);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 撤销选择：回退到不选当前元素的状态</span></span><br><span class="line">            path.remove(path.size() - <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>算法解析</strong>：</p><ul><li><strong>选择</strong>：对于每个元素，都有两种选择：选或不选</li><li><strong>约束</strong>：无特殊约束，所有组合都有效</li><li><strong>目标</strong>：生成所有可能的子集</li><li><strong>关键</strong>：在递归开始就记录当前路径，因为每个状态都是有效子集</li></ul><p><strong>示例</strong>：<br>输入：<code>[1, 2, 3]</code><br>输出：<code>[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]</code></p><h3 id="3-全排列问题-⭐⭐⭐☆">3. 全排列问题 ⭐⭐⭐☆</h3><p><strong>LeetCode 46</strong>：给定一个没有重复数字的序列，返回其所有可能的全排列。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Permutations</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> List&lt;List&lt;Integer&gt;&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">private</span> List&lt;Integer&gt; path = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span>[] used;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;List&lt;Integer&gt;&gt; <span class="title function_">permute</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">        used = <span class="keyword">new</span> <span class="title class_">boolean</span>[nums.length];</span><br><span class="line">        backtrack(nums);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">        <span class="comment">// 终止条件：路径包含所有元素</span></span><br><span class="line">        <span class="keyword">if</span> (path.size() == nums.length) &#123;</span><br><span class="line">            result.add(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(path));</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 遍历所有选择</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; nums.length; i++) &#123;</span><br><span class="line">            <span class="comment">// 跳过已使用的元素</span></span><br><span class="line">            <span class="keyword">if</span> (used[i]) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 做选择</span></span><br><span class="line">            path.add(nums[i]);</span><br><span class="line">            used[i] = <span class="literal">true</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 递归探索</span></span><br><span class="line">            backtrack(nums);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 撤销选择</span></span><br><span class="line">            path.remove(path.size() - <span class="number">1</span>);</span><br><span class="line">            used[i] = <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-N皇后问题-⭐⭐⭐⭐">4. N皇后问题 ⭐⭐⭐⭐</h3><p><strong>LeetCode 51</strong>：经典的N皇后问题，在N×N棋盘上放置N个皇后，使其不能互相攻击。</p><p><img src="https://markpic.adoreorg.cn/2025/09/algo/solution_4_queens.png" alt="N皇后问题4皇后解决方案"></p><h4 id="方法一：基础回溯算法">方法一：基础回溯算法</h4><p><strong>算法思路</strong></p><p>为了确保皇后之间互不冲突，我们需要满足三个约束条件：<strong>列、主对角线和次对角线</strong>约束。</p><p><img src="https://markpic.adoreorg.cn/2025/09/algo/n_queens_constraints.png" alt="N皇后问题约束条件分析"></p><p><strong>约束条件处理</strong></p><p><strong>1. 列约束</strong></p><ul><li>使用长度为 <code>n</code> 的布尔数组 <code>cols</code>，其中 <code>cols[col]</code> 表示第 <code>col</code> 列是否已被占用</li><li>在放置皇后前检查 <code>cols[col]</code>，若为 <code>false</code> 则可放置，否则剪枝</li><li>回溯时动态更新 <code>cols</code> 的状态</li></ul><p><strong>2. 主对角线约束</strong></p><ul><li>主对角线（左上到右下）上的所有格子满足 <strong>行索引 - 列索引 = 常数</strong></li><li>使用长度为 <code>2n - 1</code> 的数组 <code>diags1</code>，其中 <code>diags1[row - col + n - 1]</code> 标记该对角线是否被占用</li><li><strong>映射逻辑</strong>：<code>row - col</code> 的范围为 <code>[-(n-1), n-1]</code>，通过 <code>+ n - 1</code> 将其转换为非负索引 <code>[0, 2n-2]</code></li></ul><p><strong>3. 次对角线约束</strong></p><ul><li>次对角线（右上到左下）上的所有格子满足 <strong>行索引 + 列索引 = 常数</strong></li><li>使用长度为 <code>2n - 1</code> 的数组 <code>diags2</code>，直接以 <code>row + col</code> 作为索引（范围为 <code>[0, 2n-2]</code>）</li></ul><p><strong>优化要点</strong></p><table><thead><tr><th>优化维度</th><th>具体实现</th><th>复杂度</th></tr></thead><tbody><tr><td><strong>空间优化</strong></td><td>仅需 3 个数组（<code>cols</code>、<code>diags1</code>、<code>diags2</code>）即可覆盖所有约束</td><td><code>O(n)</code></td></tr><tr><td><strong>剪枝效率</strong></td><td>每次放置前检查三个数组</td><td><code>O(1)</code></td></tr><tr><td><strong>索引映射</strong></td><td>主对角线的 <code>row - col</code> 可能为负，通过 <code>+ n - 1</code> 偏移确保数组访问合法</td><td>-</td></tr></tbody></table><p><img src="https://markpic.adoreorg.cn/2025/09/algo/n_queens_cols_diagonals.png" alt="N皇后问题对角线索引映射"></p><p><strong>具体示例</strong></p><p><strong>n = 4 时</strong>：</p><ul><li><code>diags1</code> 和 <code>diags2</code> 的长度均为 <code>7</code>（<code>2 * 4 - 1</code>）</li><li>格子 <code>(1, 2)</code> 的主对角线索引：<code>1 - 2 + 3 = 2</code>；次对角线索引：<code>1 + 2 = 3</code></li></ul><p>这种设计以最小开销实现了对棋盘约束的精确控制，是回溯算法剪枝的经典实践。</p><p><img src="https://markpic.adoreorg.cn/2025/09/algo/n_queens_placing.png" alt="N皇后问题放置过程"></p><p><strong>代码实现</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> List&lt;List&lt;String&gt;&gt; <span class="title function_">solveNQueens</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">        List&lt;List&lt;String&gt;&gt; res = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="type">char</span>[][] board = <span class="keyword">new</span> <span class="title class_">char</span>[n][n];</span><br><span class="line">        <span class="comment">// 初始化棋盘</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">                board[i][j] = <span class="string">&#x27;.&#x27;</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">boolean</span>[] cols = <span class="keyword">new</span> <span class="title class_">boolean</span>[n]; <span class="comment">// 列是否占用</span></span><br><span class="line">        <span class="type">boolean</span>[] diags1 = <span class="keyword">new</span> <span class="title class_">boolean</span>[<span class="number">2</span> * n - <span class="number">1</span>]; <span class="comment">// 主对角线是否占用</span></span><br><span class="line">        <span class="type">boolean</span>[] diags2 = <span class="keyword">new</span> <span class="title class_">boolean</span>[<span class="number">2</span> * n - <span class="number">1</span>]; <span class="comment">// 次对角线是否占用</span></span><br><span class="line">        backtrack(<span class="number">0</span>, n, board, res, cols, diags1, diags2);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span> row, <span class="type">int</span> n, <span class="type">char</span>[][] board, List&lt;List&lt;String&gt;&gt; res,</span></span><br><span class="line"><span class="params">                          <span class="type">boolean</span>[] cols, <span class="type">boolean</span>[] diags1, <span class="type">boolean</span>[] diags2)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (row == n) &#123;</span><br><span class="line">            <span class="comment">// 将当前棋盘状态转换为字符串列表，加入结果</span></span><br><span class="line">            List&lt;String&gt; solution = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">char</span>[] r : board) &#123;</span><br><span class="line">                solution.add(<span class="keyword">new</span> <span class="title class_">String</span>(r));</span><br><span class="line">            &#125;</span><br><span class="line">            res.add(solution);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">col</span> <span class="operator">=</span> <span class="number">0</span>; col &lt; n; col++) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">diag1</span> <span class="operator">=</span> row - col + n - <span class="number">1</span>;</span><br><span class="line">            <span class="type">int</span> <span class="variable">diag2</span> <span class="operator">=</span> row + col;</span><br><span class="line">            <span class="comment">// 检查当前位置是否可以放置皇后</span></span><br><span class="line">            <span class="keyword">if</span> (!cols[col] &amp;&amp; !diags1[diag1] &amp;&amp; !diags2[diag2]) &#123;</span><br><span class="line">                board[row][col] = <span class="string">&#x27;Q&#x27;</span>;</span><br><span class="line">                cols[col] = diags1[diag1] = diags2[diag2] = <span class="literal">true</span>;</span><br><span class="line">                <span class="comment">// 递归处理下一行</span></span><br><span class="line">                backtrack(row + <span class="number">1</span>, n, board, res, cols, diags1, diags2);</span><br><span class="line">                <span class="comment">// 回溯：撤销选择</span></span><br><span class="line">                board[row][col] = <span class="string">&#x27;.&#x27;</span>;</span><br><span class="line">                cols[col] = diags1[diag1] = diags2[diag2] = <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="方法二：基础回溯算法">方法二：基础回溯算法</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NQueens</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> List&lt;List&lt;String&gt;&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">private</span> <span class="type">char</span>[][] board;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> n;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;List&lt;String&gt;&gt; <span class="title function_">solveNQueens</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.n = n;</span><br><span class="line">        <span class="built_in">this</span>.board = <span class="keyword">new</span> <span class="title class_">char</span>[n][n];</span><br><span class="line">        <span class="comment">// 初始化棋盘</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">char</span>[] row : board) &#123;</span><br><span class="line">            Arrays.fill(row, <span class="string">&#x27;.&#x27;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        backtrack(<span class="number">0</span>);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span> row)</span> &#123;</span><br><span class="line">        <span class="comment">// 终止条件：所有行都放置了皇后</span></span><br><span class="line">        <span class="keyword">if</span> (row == n) &#123;</span><br><span class="line">            result.add(boardToList());</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 遍历当前行的所有列</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">col</span> <span class="operator">=</span> <span class="number">0</span>; col &lt; n; col++) &#123;</span><br><span class="line">            <span class="comment">// 检查当前位置是否安全</span></span><br><span class="line">            <span class="keyword">if</span> (isValid(row, col)) &#123;</span><br><span class="line">                <span class="comment">// 做选择：放置皇后</span></span><br><span class="line">                board[row][col] = <span class="string">&#x27;Q&#x27;</span>;</span><br><span class="line">                <span class="comment">// 递归探索下一行</span></span><br><span class="line">                backtrack(row + <span class="number">1</span>);</span><br><span class="line">                <span class="comment">// 撤销选择</span></span><br><span class="line">                board[row][col] = <span class="string">&#x27;.&#x27;</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">isValid</span><span class="params">(<span class="type">int</span> row, <span class="type">int</span> col)</span> &#123;</span><br><span class="line">        <span class="comment">// 检查列冲突</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; row; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (board[i][col] == <span class="string">&#x27;Q&#x27;</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 检查左上对角线</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> row - <span class="number">1</span>, j = col - <span class="number">1</span>; i &gt;= <span class="number">0</span> &amp;&amp; j &gt;= <span class="number">0</span>; i--, j--) &#123;</span><br><span class="line">            <span class="keyword">if</span> (board[i][j] == <span class="string">&#x27;Q&#x27;</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 检查右上对角线</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> row - <span class="number">1</span>, j = col + <span class="number">1</span>; i &gt;= <span class="number">0</span> &amp;&amp; j &lt; n; i--, j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (board[i][j] == <span class="string">&#x27;Q&#x27;</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> List&lt;String&gt; <span class="title function_">boardToList</span><span class="params">()</span> &#123;</span><br><span class="line">        List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">char</span>[] row : board) &#123;</span><br><span class="line">            list.add(<span class="keyword">new</span> <span class="title class_">String</span>(row));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> list;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="✂️-剪枝优化技巧">✂️ 剪枝优化技巧</h2><h3 id="1-排序剪枝">1. 排序剪枝</h3><p>通过排序可以提前终止不可能的分支：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 原始版本</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> startIndex; i &lt; candidates.length; i++) &#123;</span><br><span class="line">    path.add(candidates[i]);</span><br><span class="line">    <span class="keyword">if</span> (sum + candidates[i] &gt; target) &#123;  <span class="comment">// 发现不合适才回溯</span></span><br><span class="line">        path.remove(path.size() - <span class="number">1</span>);</span><br><span class="line">        <span class="keyword">continue</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ... 后续处理</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 优化版本：先排序，提前剪枝</span></span><br><span class="line">Arrays.sort(candidates);</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> startIndex; i &lt; candidates.length; i++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (sum + candidates[i] &gt; target) &#123;</span><br><span class="line">        <span class="keyword">break</span>;  <span class="comment">// 后续元素更大，直接剪枝</span></span><br><span class="line">    &#125;</span><br><span class="line">    path.add(candidates[i]);</span><br><span class="line">    <span class="comment">// ... 后续处理</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-重复性剪枝">2. 重复性剪枝</h3><p>处理有重复元素的情况：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 处理重复元素，避免生成重复解</span></span><br><span class="line"><span class="keyword">if</span> (i &gt; startIndex &amp;&amp; candidates[i] == candidates[i-<span class="number">1</span>]) &#123;</span><br><span class="line">    <span class="keyword">continue</span>;  <span class="comment">// 跳过重复元素</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-可行性剪枝">3. 可行性剪枝</h3><p>提前判断当前路径是否可能达到目标：</p><p><strong>🎯 情况1：剩余元素和不足以达到目标</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 计算剩余元素的最小可能和</span></span><br><span class="line"><span class="type">int</span> <span class="variable">remainingMinSum</span> <span class="operator">=</span> calculateMinSum(candidates, i);</span><br><span class="line"><span class="keyword">if</span> (sum + remainingMinSum &gt; target) &#123;</span><br><span class="line">    <span class="keyword">break</span>;  <span class="comment">// 即使取最小值也超过目标，剪枝</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>🎯 情况2：剩余元素数量不足以满足选择数量要求</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 经典例子：组合总和III（需要选择k个数）</span></span><br><span class="line"><span class="comment">// 剩余可选数字数量必须 &gt;= 还需要选择的数字数量</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> startIndex; i &lt;= <span class="number">9</span> - (k - path.size()) + <span class="number">1</span>; i++) &#123;</span><br><span class="line">    <span class="comment">// 🔍 剪枝条件：剩余数字数量不足以凑齐k个数</span></span><br><span class="line">    <span class="comment">// 9 - i + 1：从i到9的剩余数字数量</span></span><br><span class="line">    <span class="comment">// (k - path.size())：还需要选择的数字数量</span></span><br><span class="line">    <span class="comment">// 确保：剩余数字数量 &gt;= 还需要选择的数字数量</span></span><br><span class="line">    </span><br><span class="line">    path.add(i);           <span class="comment">// ✅ 做选择</span></span><br><span class="line">    backtrack(result, path, k, i + <span class="number">1</span>, sum + i);  <span class="comment">// 🚀 递归</span></span><br><span class="line">    path.remove(path.size() - <span class="number">1</span>);  <span class="comment">// ↩️ 撤销选择</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>🎯 情况3：剩余元素无法满足特定约束条件</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 例如：需要选择k个元素，但剩余元素不足</span></span><br><span class="line"><span class="keyword">if</span> (candidates.length - i &lt; k - path.size()) &#123;</span><br><span class="line">    <span class="keyword">break</span>;  <span class="comment">// 🔪 剩余元素数量不足，直接剪枝</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 或者：需要达到target，但剩余最大可能值仍不够</span></span><br><span class="line"><span class="type">int</span> <span class="variable">remainingMaxSum</span> <span class="operator">=</span> calculateMaxSum(candidates, i);</span><br><span class="line"><span class="keyword">if</span> (sum + remainingMaxSum &lt; target) &#123;</span><br><span class="line">    <span class="keyword">break</span>;  <span class="comment">// 🔪 即使取最大值也达不到目标，剪枝</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="🏢-实际应用案例">🏢 实际应用案例</h2><h3 id="案例1：任务调度问题">案例1：任务调度问题</h3><p><strong>场景描述</strong>：有N个任务，每个任务有截止时间和完成所需时间，安排任务执行顺序，使得尽可能多的任务按时完成。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TaskScheduling</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Task</span> &#123;</span><br><span class="line">        <span class="type">int</span> deadline;</span><br><span class="line">        <span class="type">int</span> duration;</span><br><span class="line">        <span class="type">int</span> id;</span><br><span class="line">        </span><br><span class="line">        Task(<span class="type">int</span> id, <span class="type">int</span> deadline, <span class="type">int</span> duration) &#123;</span><br><span class="line">            <span class="built_in">this</span>.id = id;</span><br><span class="line">            <span class="built_in">this</span>.deadline = deadline;</span><br><span class="line">            <span class="built_in">this</span>.duration = duration;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> List&lt;Integer&gt; bestSchedule;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> maxTasks;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;Integer&gt; <span class="title function_">scheduleTasks</span><span class="params">(List&lt;Task&gt; tasks)</span> &#123;</span><br><span class="line">        bestSchedule = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        maxTasks = <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 按截止时间排序</span></span><br><span class="line">        tasks.sort((a, b) -&gt; a.deadline - b.deadline);</span><br><span class="line">        </span><br><span class="line">        backtrack(tasks, <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(), <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">        <span class="keyword">return</span> bestSchedule;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(List&lt;Task&gt; tasks, List&lt;Integer&gt; currentSchedule, </span></span><br><span class="line"><span class="params">                          <span class="type">int</span> currentTime, <span class="type">int</span> taskIndex)</span> &#123;</span><br><span class="line">        <span class="comment">// 更新最优解</span></span><br><span class="line">        <span class="keyword">if</span> (currentSchedule.size() &gt; maxTasks) &#123;</span><br><span class="line">            maxTasks = currentSchedule.size();</span><br><span class="line">            bestSchedule = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(currentSchedule);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 剪枝：如果剩余任务即使全部完成也无法超越当前最优解</span></span><br><span class="line">        <span class="keyword">if</span> (currentSchedule.size() + (tasks.size() - taskIndex) &lt;= maxTasks) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 遍历剩余任务</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> taskIndex; i &lt; tasks.size(); i++) &#123;</span><br><span class="line">            <span class="type">Task</span> <span class="variable">task</span> <span class="operator">=</span> tasks.get(i);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 检查是否可以在截止时间前完成</span></span><br><span class="line">            <span class="keyword">if</span> (currentTime + task.duration &lt;= task.deadline) &#123;</span><br><span class="line">                <span class="comment">// 做选择</span></span><br><span class="line">                currentSchedule.add(task.id);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 递归探索</span></span><br><span class="line">                backtrack(tasks, currentSchedule, currentTime + task.duration, i + <span class="number">1</span>);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 撤销选择</span></span><br><span class="line">                currentSchedule.remove(currentSchedule.size() - <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="案例2：资源分配优化">案例2：资源分配优化</h3><p><strong>场景描述</strong>：将有限的资源分配给多个项目，每个项目有不同的收益和资源需求，找到收益最大的分配方案。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ResourceAllocation</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Project</span> &#123;</span><br><span class="line">        <span class="type">int</span> resourceNeeded;</span><br><span class="line">        <span class="type">int</span> profit;</span><br><span class="line">        <span class="type">int</span> id;</span><br><span class="line">        </span><br><span class="line">        Project(<span class="type">int</span> id, <span class="type">int</span> resourceNeeded, <span class="type">int</span> profit) &#123;</span><br><span class="line">            <span class="built_in">this</span>.id = id;</span><br><span class="line">            <span class="built_in">this</span>.resourceNeeded = resourceNeeded;</span><br><span class="line">            <span class="built_in">this</span>.profit = profit;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> maxProfit;</span><br><span class="line">    <span class="keyword">private</span> List&lt;Integer&gt; bestAllocation;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;Integer&gt; <span class="title function_">allocateResources</span><span class="params">(List&lt;Project&gt; projects, <span class="type">int</span> totalResource)</span> &#123;</span><br><span class="line">        maxProfit = <span class="number">0</span>;</span><br><span class="line">        bestAllocation = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 按单位资源收益排序，优先处理高收益项目</span></span><br><span class="line">        projects.sort((a, b) -&gt; (b.profit * a.resourceNeeded - a.profit * b.resourceNeeded));</span><br><span class="line">        </span><br><span class="line">        backtrack(projects, totalResource, <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(), <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">        <span class="keyword">return</span> bestAllocation;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(List&lt;Project&gt; projects, <span class="type">int</span> remainingResource, </span></span><br><span class="line"><span class="params">                          List&lt;Integer&gt; currentAllocation, <span class="type">int</span> currentProfit, <span class="type">int</span> startIndex)</span> &#123;</span><br><span class="line">        <span class="comment">// 更新最优解</span></span><br><span class="line">        <span class="keyword">if</span> (currentProfit &gt; maxProfit) &#123;</span><br><span class="line">            maxProfit = currentProfit;</span><br><span class="line">            bestAllocation = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(currentAllocation);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 剪枝：如果剩余项目即使全部选择也无法超越当前最优解</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">remainingMaxProfit</span> <span class="operator">=</span> calculateMaxPossibleProfit(projects, startIndex, remainingResource);</span><br><span class="line">        <span class="keyword">if</span> (currentProfit + remainingMaxProfit &lt;= maxProfit) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 遍历剩余项目</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> startIndex; i &lt; projects.size(); i++) &#123;</span><br><span class="line">            <span class="type">Project</span> <span class="variable">project</span> <span class="operator">=</span> projects.get(i);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 检查资源是否足够</span></span><br><span class="line">            <span class="keyword">if</span> (project.resourceNeeded &lt;= remainingResource) &#123;</span><br><span class="line">                <span class="comment">// 做选择</span></span><br><span class="line">                currentAllocation.add(project.id);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 递归探索</span></span><br><span class="line">                backtrack(projects, remainingResource - project.resourceNeeded, </span><br><span class="line">                         currentAllocation, currentProfit + project.profit, i + <span class="number">1</span>);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 撤销选择</span></span><br><span class="line">                currentAllocation.remove(currentAllocation.size() - <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="title function_">calculateMaxPossibleProfit</span><span class="params">(List&lt;Project&gt; projects, <span class="type">int</span> startIndex, <span class="type">int</span> remainingResource)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">profit</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">resource</span> <span class="operator">=</span> remainingResource;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> startIndex; i &lt; projects.size() &amp;&amp; resource &gt; <span class="number">0</span>; i++) &#123;</span><br><span class="line">            <span class="type">Project</span> <span class="variable">project</span> <span class="operator">=</span> projects.get(i);</span><br><span class="line">            <span class="keyword">if</span> (project.resourceNeeded &lt;= resource) &#123;</span><br><span class="line">                profit += project.profit;</span><br><span class="line">                resource -= project.resourceNeeded;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 部分选择</span></span><br><span class="line">                profit += project.profit * resource / project.resourceNeeded;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> profit;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="⚡-性能分析">⚡ 性能分析</h2><h3 id="时间复杂度">时间复杂度</h3><p>回溯算法的时间复杂度通常是指数级的：</p><table><thead><tr><th>问题类型</th><th>时间复杂度</th><th>说明</th></tr></thead><tbody><tr><td>子集问题</td><td>O(2^n)</td><td>每个元素都有选或不选两种选择</td></tr><tr><td>排列问题</td><td>O(n!)</td><td>n个元素的全排列</td></tr><tr><td>组合问题</td><td>O(C(n,k))</td><td>从n个元素中选择k个</td></tr><tr><td>N皇后</td><td>O(n!)</td><td>每行放置一个皇后</td></tr></tbody></table><h3 id="空间复杂度">空间复杂度</h3><ul><li><strong>递归栈空间</strong>：O(n) 到 O(h)，h是递归深度</li><li><strong>路径存储</strong>：O(n) 存储当前路径</li><li><strong>结果存储</strong>：O(所有解的总大小)</li></ul><h3 id="优化效果">优化效果</h3><p>通过剪枝优化可以显著减少实际搜索空间：</p><ul><li><strong>排序剪枝</strong>：通常可以减少50%以上的搜索节点</li><li><strong>可行性剪枝</strong>：根据问题特性，可能减少90%以上</li><li><strong>重复性剪枝</strong>：完全避免重复解的生成</li></ul><hr><h2 id="🏋️-实战练习">🏋️ 实战练习</h2><h3 id="练习题1：电话号码的字母组合-📞">练习题1：电话号码的字母组合 📞</h3><p><strong>LeetCode 17</strong>：给定一个仅包含数字2-9的字符串，返回所有可能的字母组合。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LetterCombinations</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String[] LETTERS = &#123;</span><br><span class="line">        <span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>, <span class="string">&quot;abc&quot;</span>, <span class="string">&quot;def&quot;</span>, <span class="string">&quot;ghi&quot;</span>, <span class="string">&quot;jkl&quot;</span>, <span class="string">&quot;mno&quot;</span>, <span class="string">&quot;pqrs&quot;</span>, <span class="string">&quot;tuv&quot;</span>, <span class="string">&quot;wxyz&quot;</span></span><br><span class="line">    &#125;;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> List&lt;String&gt; result;</span><br><span class="line">    <span class="keyword">private</span> StringBuilder path;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;String&gt; <span class="title function_">letterCombinations</span><span class="params">(String digits)</span> &#123;</span><br><span class="line">        result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="keyword">if</span> (digits == <span class="literal">null</span> || digits.length() == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> result;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        path = <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line">        backtrack(digits, <span class="number">0</span>);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(String digits, <span class="type">int</span> index)</span> &#123;</span><br><span class="line">        <span class="comment">// 🎯 终止条件：处理完所有数字</span></span><br><span class="line">        <span class="keyword">if</span> (index == digits.length()) &#123;</span><br><span class="line">            result.add(path.toString());</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔍 获取当前数字对应的字母</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">digit</span> <span class="operator">=</span> digits.charAt(index) - <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">        <span class="type">String</span> <span class="variable">letters</span> <span class="operator">=</span> LETTERS[digit];</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔄 遍历当前数字的所有可能字母</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">char</span> letter : letters.toCharArray()) &#123;</span><br><span class="line">            <span class="comment">// ✅ 做选择</span></span><br><span class="line">            path.append(letter);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 🚀 递归处理下一个数字</span></span><br><span class="line">            backtrack(digits, index + <span class="number">1</span>);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// ↩️ 撤销选择</span></span><br><span class="line">            path.deleteCharAt(path.length() - <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="练习题2：括号生成-🎯">练习题2：括号生成 🎯</h3><p><strong>LeetCode 22</strong>：数字n代表生成括号的对数，设计一个函数生成所有可能的且有效的括号组合。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GenerateParentheses</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> List&lt;String&gt; result;</span><br><span class="line">    <span class="keyword">private</span> StringBuilder path;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;String&gt; <span class="title function_">generateParenthesis</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">        result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        path = <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line">        backtrack(n, n);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span> leftRemaining, <span class="type">int</span> rightRemaining)</span> &#123;</span><br><span class="line">        <span class="comment">// 🎯 终止条件：所有括号都用完</span></span><br><span class="line">        <span class="keyword">if</span> (leftRemaining == <span class="number">0</span> &amp;&amp; rightRemaining == <span class="number">0</span>) &#123;</span><br><span class="line">            result.add(path.toString());</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ➕ 添加左括号（如果还有剩余的）</span></span><br><span class="line">        <span class="keyword">if</span> (leftRemaining &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            path.append(<span class="string">&#x27;(&#x27;</span>);</span><br><span class="line">            backtrack(leftRemaining - <span class="number">1</span>, rightRemaining);</span><br><span class="line">            path.deleteCharAt(path.length() - <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ➖ 添加右括号（如果还有剩余的且不会导致无效）</span></span><br><span class="line">        <span class="keyword">if</span> (rightRemaining &gt; leftRemaining) &#123;</span><br><span class="line">            path.append(<span class="string">&#x27;)&#x27;</span>);</span><br><span class="line">            backtrack(leftRemaining, rightRemaining - <span class="number">1</span>);</span><br><span class="line">            path.deleteCharAt(path.length() - <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="练习题3：单词搜索-🔍">练习题3：单词搜索 🔍</h3><p><strong>LeetCode 79</strong>：给定一个二维网格和一个单词，找出该单词是否存在于网格中。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WordSearch</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span>[][] DIRECTIONS = &#123;&#123;-<span class="number">1</span>,<span class="number">0</span>&#125;, &#123;<span class="number">1</span>,<span class="number">0</span>&#125;, &#123;<span class="number">0</span>,-<span class="number">1</span>&#125;, &#123;<span class="number">0</span>,<span class="number">1</span>&#125;&#125;;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">char</span>[][] board;</span><br><span class="line">    <span class="keyword">private</span> String word;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span>[][] visited;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> rows, cols;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">exist</span><span class="params">(<span class="type">char</span>[][] board, String word)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (board == <span class="literal">null</span> || board.length == <span class="number">0</span> || word == <span class="literal">null</span> || word.length() == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="built_in">this</span>.board = board;</span><br><span class="line">        <span class="built_in">this</span>.word = word;</span><br><span class="line">        <span class="built_in">this</span>.rows = board.length;</span><br><span class="line">        <span class="built_in">this</span>.cols = board[<span class="number">0</span>].length;</span><br><span class="line">        <span class="built_in">this</span>.visited = <span class="keyword">new</span> <span class="title class_">boolean</span>[rows][cols];</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔍 从每个位置开始搜索</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; rows; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; cols; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (board[i][j] == word.charAt(<span class="number">0</span>)) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (backtrack(i, j, <span class="number">0</span>)) &#123;</span><br><span class="line">                        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span> row, <span class="type">int</span> col, <span class="type">int</span> index)</span> &#123;</span><br><span class="line">        <span class="comment">// 🎯 终止条件：找到完整单词</span></span><br><span class="line">        <span class="keyword">if</span> (index == word.length() - <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ✅ 标记当前位置已访问</span></span><br><span class="line">        visited[row][col] = <span class="literal">true</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 探索四个方向</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span>[] dir : DIRECTIONS) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">newRow</span> <span class="operator">=</span> row + dir[<span class="number">0</span>];</span><br><span class="line">            <span class="type">int</span> <span class="variable">newCol</span> <span class="operator">=</span> col + dir[<span class="number">1</span>];</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 🔍 检查新位置是否有效</span></span><br><span class="line">            <span class="keyword">if</span> (isValid(newRow, newCol) &amp;&amp; !visited[newRow][newCol] &amp;&amp; </span><br><span class="line">                board[newRow][newCol] == word.charAt(index + <span class="number">1</span>)) &#123;</span><br><span class="line">                </span><br><span class="line">                <span class="keyword">if</span> (backtrack(newRow, newCol, index + <span class="number">1</span>)) &#123;</span><br><span class="line">                    visited[row][col] = <span class="literal">false</span>; <span class="comment">// ↩️ 回溯</span></span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ↩️ 撤销选择</span></span><br><span class="line">        visited[row][col] = <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">isValid</span><span class="params">(<span class="type">int</span> row, <span class="type">int</span> col)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> row &gt;= <span class="number">0</span> &amp;&amp; row &lt; rows &amp;&amp; col &gt;= <span class="number">0</span> &amp;&amp; col &lt; cols;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="练习题4：分割回文串-✨">练习题4：分割回文串 ✨</h3><p><strong>LeetCode 131</strong>：给定一个字符串s，将s分割成一些子串，使每个子串都是回文串。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PalindromePartitioning</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> List&lt;List&lt;String&gt;&gt; result;</span><br><span class="line">    <span class="keyword">private</span> List&lt;String&gt; path;</span><br><span class="line">    <span class="keyword">private</span> String s;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;List&lt;String&gt;&gt; <span class="title function_">partition</span><span class="params">(String s)</span> &#123;</span><br><span class="line">        result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        path = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="built_in">this</span>.s = s;</span><br><span class="line">        backtrack(<span class="number">0</span>);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span> startIndex)</span> &#123;</span><br><span class="line">        <span class="comment">// 🎯 终止条件：处理完整个字符串</span></span><br><span class="line">        <span class="keyword">if</span> (startIndex == s.length()) &#123;</span><br><span class="line">            result.add(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(path));</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔍 尝试所有可能的分割点</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">end</span> <span class="operator">=</span> startIndex; end &lt; s.length(); end++) &#123;</span><br><span class="line">            <span class="comment">// ✂️ 如果当前子串是回文，则进行分割</span></span><br><span class="line">            <span class="keyword">if</span> (isPalindrome(startIndex, end)) &#123;</span><br><span class="line">                <span class="type">String</span> <span class="variable">substring</span> <span class="operator">=</span> s.substring(startIndex, end + <span class="number">1</span>);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// ✅ 做选择</span></span><br><span class="line">                path.add(substring);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 🚀 递归处理剩余部分</span></span><br><span class="line">                backtrack(end + <span class="number">1</span>);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// ↩️ 撤销选择</span></span><br><span class="line">                path.remove(path.size() - <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">isPalindrome</span><span class="params">(<span class="type">int</span> start, <span class="type">int</span> end)</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (start &lt; end) &#123;</span><br><span class="line">            <span class="keyword">if</span> (s.charAt(start) != s.charAt(end)) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            start++;</span><br><span class="line">            end--;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="📚-相关LeetCode题目">📚 相关LeetCode题目</h2><h3 id="基础题目">基础题目</h3><ul><li><a href="https://leetcode.com/problems/combinations/">77. 组合</a></li><li><a href="https://leetcode.com/problems/permutations/">46. 全排列</a></li><li><a href="https://leetcode.com/problems/subsets/">78. 子集</a></li><li><a href="https://leetcode.com/problems/letter-combinations-of-a-phone-number/">17. 电话号码的字母组合</a></li></ul><h3 id="进阶题目">进阶题目</h3><ul><li><a href="https://leetcode.com/problems/combination-sum/">39. 组合总和</a></li><li><a href="https://leetcode.com/problems/combination-sum-ii/">40. 组合总和 II</a></li><li><a href="https://leetcode.com/problems/generate-parentheses/">22. 括号生成</a></li><li><a href="https://leetcode.com/problems/word-search/">79. 单词搜索</a></li></ul><h3 id="高难度题目">高难度题目</h3><ul><li><a href="https://leetcode.com/problems/n-queens/">51. N皇后</a></li><li><a href="https://leetcode.com/problems/n-queens-ii/">52. N皇后 II</a></li><li><a href="https://leetcode.com/problems/sudoku-solver/">37. 解数独</a></li><li><a href="https://leetcode.com/problems/palindrome-partitioning/">131. 分割回文串</a></li></ul><hr><h2 id="🎯-关键要点总结">🎯 关键要点总结</h2><h3 id="核心原则">核心原则</h3><ol><li><strong>明确选择</strong>：清楚地定义每一步的选择是什么</li><li><strong>路径记录</strong>：维护当前的选择路径</li><li><strong>终止条件</strong>：确定何时停止搜索</li><li><strong>回溯机制</strong>：能够撤销选择，回到之前的状态</li></ol><h3 id="常见陷阱">常见陷阱</h3><ol><li><strong>忘记回溯</strong>：选择后忘记撤销，导致状态错误</li><li><strong>重复选择</strong>：没有正确处理已选择的元素</li><li><strong>边界条件</strong>：数组越界、空指针等边界问题</li><li><strong>性能问题</strong>：没有剪枝导致搜索空间过大</li></ol><h3 id="优化策略">优化策略</h3><ol><li><strong>排序剪枝</strong>：先排序，提前终止不可能的分支</li><li><strong>重复性剪枝</strong>：跳过重复的选择，避免重复解</li><li><strong>可行性剪枝</strong>：提前判断当前路径是否可能成功</li><li><strong>最优性剪枝</strong>：在优化问题中，提前判断是否可能超越当前最优解</li></ol><h3 id="调试技巧">调试技巧</h3><ol><li><strong>打印路径</strong>：在关键节点打印当前路径</li><li><strong>限制搜索</strong>：先处理小规模问题，验证算法正确性</li><li><strong>可视化</strong>：画出决策树，帮助理解搜索过程</li><li><strong>逐步调试</strong>：使用IDE的调试功能，逐步跟踪回溯过程</li></ol><hr><h2 id="🎯-回溯算法模板总结">🎯 回溯算法模板总结</h2><h3 id="模板1：组合-子集问题-🧩">模板1：组合/子集问题 🧩</h3><p><strong>适用场景</strong>：从一组元素中选择若干个，不考虑顺序</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CombinationTemplate</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> List&lt;List&lt;Integer&gt;&gt; result;</span><br><span class="line">    <span class="keyword">private</span> List&lt;Integer&gt; path;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;List&lt;Integer&gt;&gt; <span class="title function_">combine</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">        result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        path = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        backtrack(nums, <span class="number">0</span>);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> startIndex)</span> &#123;</span><br><span class="line">        <span class="comment">// 🎯 收集所有路径（包括空路径）</span></span><br><span class="line">        result.add(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(path));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔄 遍历选择列表</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> startIndex; i &lt; nums.length; i++) &#123;</span><br><span class="line">            <span class="comment">// ✅ 做选择</span></span><br><span class="line">            path.add(nums[i]);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 🚀 递归探索（注意i+1避免重复使用）</span></span><br><span class="line">            backtrack(nums, i + <span class="number">1</span>);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// ↩️ 撤销选择</span></span><br><span class="line">            path.remove(path.size() - <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键特征</strong>：</p><ul><li>使用<code>startIndex</code>避免重复选择</li><li>每个元素都有&quot;选&quot;或&quot;不选&quot;两种选择</li><li>时间复杂度：O(2^n)</li></ul><h3 id="模板2：排列问题-🔄">模板2：排列问题 🔄</h3><p><strong>适用场景</strong>：考虑元素顺序的所有可能排列</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PermutationTemplate</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> List&lt;List&lt;Integer&gt;&gt; result;</span><br><span class="line">    <span class="keyword">private</span> List&lt;Integer&gt; path;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span>[] used;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;List&lt;Integer&gt;&gt; <span class="title function_">permute</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">        result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        path = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        used = <span class="keyword">new</span> <span class="title class_">boolean</span>[nums.length];</span><br><span class="line">        backtrack(nums);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">        <span class="comment">// 🎯 终止条件：路径包含所有元素</span></span><br><span class="line">        <span class="keyword">if</span> (path.size() == nums.length) &#123;</span><br><span class="line">            result.add(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(path));</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔄 遍历所有选择</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; nums.length; i++) &#123;</span><br><span class="line">            <span class="comment">// ❌ 跳过已使用的元素</span></span><br><span class="line">            <span class="keyword">if</span> (used[i]) <span class="keyword">continue</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// ✅ 做选择</span></span><br><span class="line">            path.add(nums[i]);</span><br><span class="line">            used[i] = <span class="literal">true</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 🚀 递归探索</span></span><br><span class="line">            backtrack(nums);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// ↩️ 撤销选择</span></span><br><span class="line">            path.remove(path.size() - <span class="number">1</span>);</span><br><span class="line">            used[i] = <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键特征</strong>：</p><ul><li>使用<code>used[]</code>数组标记已使用元素</li><li>每个位置可以选择任意未使用的元素</li><li>时间复杂度：O(n!)</li></ul><h3 id="模板3：N皇后-棋盘问题-♛">模板3：N皇后/棋盘问题 ♛</h3><p><strong>适用场景</strong>：在二维空间中寻找满足约束条件的解</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NQueensTemplate</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> List&lt;List&lt;String&gt;&gt; result;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">char</span>[][] board;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> n;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;List&lt;String&gt;&gt; <span class="title function_">solveNQueens</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">        result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="built_in">this</span>.n = n;</span><br><span class="line">        <span class="built_in">this</span>.board = <span class="keyword">new</span> <span class="title class_">char</span>[n][n];</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🎨 初始化棋盘</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">char</span>[] row : board) &#123;</span><br><span class="line">            Arrays.fill(row, <span class="string">&#x27;.&#x27;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        backtrack(<span class="number">0</span>);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span> row)</span> &#123;</span><br><span class="line">        <span class="comment">// 🎯 终止条件：所有行都处理完</span></span><br><span class="line">        <span class="keyword">if</span> (row == n) &#123;</span><br><span class="line">            result.add(boardToList());</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔄 遍历当前行的所有列</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">col</span> <span class="operator">=</span> <span class="number">0</span>; col &lt; n; col++) &#123;</span><br><span class="line">            <span class="comment">// ✅ 检查约束条件</span></span><br><span class="line">            <span class="keyword">if</span> (isValid(row, col)) &#123;</span><br><span class="line">                <span class="comment">// ♛ 做选择</span></span><br><span class="line">                board[row][col] = <span class="string">&#x27;Q&#x27;</span>;</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 🚀 递归处理下一行</span></span><br><span class="line">                backtrack(row + <span class="number">1</span>);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// ↩️ 撤销选择</span></span><br><span class="line">                board[row][col] = <span class="string">&#x27;.&#x27;</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">isValid</span><span class="params">(<span class="type">int</span> row, <span class="type">int</span> col)</span> &#123;</span><br><span class="line">        <span class="comment">// 🔍 检查列冲突</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; row; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (board[i][col] == <span class="string">&#x27;Q&#x27;</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ↖️ 检查左上对角线</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> row - <span class="number">1</span>, j = col - <span class="number">1</span>; i &gt;= <span class="number">0</span> &amp;&amp; j &gt;= <span class="number">0</span>; i--, j--) &#123;</span><br><span class="line">            <span class="keyword">if</span> (board[i][j] == <span class="string">&#x27;Q&#x27;</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ↗️ 检查右上对角线</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> row - <span class="number">1</span>, j = col + <span class="number">1</span>; i &gt;= <span class="number">0</span> &amp;&amp; j &lt; n; i--, j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (board[i][j] == <span class="string">&#x27;Q&#x27;</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键特征</strong>：</p><ul><li>按行/列顺序处理，避免重复</li><li>需要复杂的约束检查</li><li>时间复杂度：O(n!)</li></ul><hr><h2 id="🧠-记忆口诀">🧠 记忆口诀</h2><h3 id="🎯-回溯算法三步法">🎯 回溯算法三步法</h3><ol><li><strong>做选择</strong>：在当前状态下选择一个选项</li><li><strong>递归探索</strong>：基于当前选择继续探索</li><li><strong>撤销选择</strong>：回到选择前的状态</li></ol><h3 id="🔍-剪枝优化口诀">🔍 剪枝优化口诀</h3><ul><li><strong>排序剪枝</strong>：“先排序，早剪枝”</li><li><strong>约束剪枝</strong>：“不满足，就跳过”</li><li><strong>最优剪枝</strong>：“不可能，就放弃”</li></ul><h3 id="🎨-问题分类记忆">🎨 问题分类记忆</h3><ul><li><strong>组合问题</strong>：“选不选，startIndex”</li><li><strong>排列问题</strong>：“用没用，used数组”</li><li><strong>棋盘问题</strong>：“行不行，约束检查”</li></ul><hr><h2 id="📖-参考资料">📖 参考资料</h2><ul><li><a href="https://book.douban.com/subject/1885170/">算法导论（第3版）</a> - 第16章 贪心算法</li><li><a href="https://book.douban.com/subject/19952400/">算法（第4版）</a> - 第5.4节 回溯算法</li><li><a href="https://leetcode.cn/tag/backtracking/">LeetCode回溯算法标签</a> 🇨🇳</li><li><a href="https://zh.wikipedia.org/wiki/%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95">回溯算法维基百科</a> 🇨🇳</li><li><a href="https://www.runoob.com/java/java-methods.html">Java回溯算法最佳实践</a> 🇨🇳</li><li><a href="https://zhuanlan.zhihu.com/p/93530380">回溯算法详解 - 知乎专栏</a> 🇨🇳</li><li><a href="https://leetcode.cn/tag/backtracking/problems/">力扣回溯算法题库</a> 🇨🇳</li></ul><hr><h2 id="🎓-学习总结：回溯算法知识图谱">🎓 学习总结：回溯算法知识图谱</h2><h3 id="🗺️-知识架构图">🗺️ 知识架构图</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">回溯算法 🌳</span><br><span class="line">├── 核心思想 (DFS + 状态撤销)</span><br><span class="line">├── 万能模板 (路径 + 选择列表 + 结束条件)</span><br><span class="line">├── 解题四步法 (分析 → 设计 → 剪枝 → 验证)</span><br><span class="line">├── 三大模板 (组合/子集 + 排列 + N皇后)</span><br><span class="line">├── 四大经典 (组合总和 + 全排列 + N皇后 + 实际应用)</span><br><span class="line">├── 优化技巧 (剪枝 + 去重 + 约束)</span><br><span class="line">└── 实战练习 (LeetCode 经典题目)</span><br></pre></td></tr></table></figure><h3 id="🎯-掌握程度自测">🎯 掌握程度自测</h3><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>解决N皇后 + 剪枝优化</td></tr><tr><td><strong>专家</strong> ⭐⭐⭐⭐</td><td>能设计复杂算法，融会贯通</td><td>解决实际业务问题</td></tr></tbody></table><h3 id="🚀-进阶学习路线">🚀 进阶学习路线</h3><ol><li><strong>🎯 基础巩固</strong>：熟练掌握三大模板</li><li><strong>⚡ 技巧提升</strong>：深入学习剪枝和去重策略</li><li><strong>🏗️ 实战应用</strong>：解决实际业务问题</li><li><strong>🔬 算法研究</strong>：探索更高效的优化算法</li></ol><h3 id="💡-学习建议">💡 学习建议</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">📝 建议1：从简单问题入手，循序渐进</span><br><span class="line">📝 建议2：多画图辅助理解，可视化搜索过程</span><br><span class="line">📝 建议3：重视剪枝优化，提升算法效率</span><br><span class="line">📝 建议4：总结模板套路，形成解题直觉</span><br><span class="line">📝 建议5：刷题 + 总结，理论结合实践</span><br></pre></td></tr></table></figure><hr><p><em>最后更新：2025-01-15</em></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;🦋 回溯算法详解：从入门到精通&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;✨ &lt;strong&gt;一文掌握回溯算法精髓&lt;/strong&gt;：系统性搜索 + 智能剪枝 = 高效解题&lt;/p&gt;
&lt;p&gt;🎯 &lt;strong&gt;核心思想&lt;/strong&gt;：试错法 + 系统性搜索 + 剪枝</summary>
      
    
    
    
    <category term="书山指路" scheme="https://blog.adoreorg.cn/categories/%E4%B9%A6%E5%B1%B1%E6%8C%87%E8%B7%AF/"/>
    
    
    <category term="算法" scheme="https://blog.adoreorg.cn/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="回溯算法" scheme="https://blog.adoreorg.cn/tags/%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95/"/>
    
    <category term="数据结构" scheme="https://blog.adoreorg.cn/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    
    <category term="LeetCode" scheme="https://blog.adoreorg.cn/tags/LeetCode/"/>
    
    <category term="ACM" scheme="https://blog.adoreorg.cn/tags/ACM/"/>
    
    <category term="DFS" scheme="https://blog.adoreorg.cn/tags/DFS/"/>
    
  </entry>
  
  <entry>
    <title>ACM模式Java输入输出处理完全指南</title>
    <link href="https://blog.adoreorg.cn/posts/eb6gs334.html"/>
    <id>https://blog.adoreorg.cn/posts/eb6gs334.html</id>
    <published>2025-01-14T16:00:00.000Z</published>
    <updated>2025-10-07T15:41:58.247Z</updated>
    
    <content type="html"><![CDATA[<h1>ACM模式Java输入输出处理完全指南</h1><blockquote><p><strong>难度</strong>: 基础到进阶</p><p><strong>标签</strong>: ACM, Java, 输入输出, 算法竞赛, 编程技巧</p><p><strong>适用场景</strong>: 算法竞赛、笔试面试、在线编程平台</p></blockquote><h2 id="📝-概述">📝 概述</h2><p>ACM模式是算法竞赛中常见的编程模式，与LeetCode的函数式编程不同，ACM模式需要手动处理输入输出。本指南涵盖Java中各种常见的输入输出处理模式。</p><h2 id="💭-基础知识">💭 基础知识</h2><h3 id="初步理解">初步理解</h3><p>ACM模式要求程序能够：</p><ol><li>读取多组测试数据</li><li>处理不同格式的输入</li><li>按照指定格式输出结果</li><li>处理输入结束条件</li></ol><h3 id="常用类和方法">常用类和方法</h3><ul><li><code>Scanner</code>类：最常用的输入处理类</li><li><code>BufferedReader</code>类：高效读取大量数据</li><li><code>StringTokenizer</code>类：字符串分割</li><li><code>System.out.print()</code>和<code>System.out.println()</code>：输出</li></ul><h2 id="🎯-常见输入输出模式">🎯 常见输入输出模式</h2><h3 id="模式一：固定数量输入">模式一：固定数量输入</h3><p><strong>场景描述</strong>：<br>输入第一行是测试用例数量n，后面跟着n行数据。</p><p><strong>输入样例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">3</span><br><span class="line">1 2</span><br><span class="line">3 4</span><br><span class="line">5 6</span><br></pre></td></tr></table></figure><p><strong>输出样例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">3</span><br><span class="line">7</span><br><span class="line">11</span><br></pre></td></tr></table></figure><p><strong>代码实现</strong>（使用Scanner）：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Scanner;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Scanner</span> <span class="variable">sc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> sc.nextInt(); <span class="comment">// 读取测试用例数量</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> sc.nextInt();</span><br><span class="line">            <span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> sc.nextInt();</span><br><span class="line">            System.out.println(a + b); <span class="comment">// 输出每行的和</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        sc.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="模式二：直到特定值结束">模式二：直到特定值结束</h3><p><strong>场景描述</strong>：<br>输入多组数据，直到遇到特定值（如0）时结束。</p><p><strong>输入样例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">5 3</span><br><span class="line">10 20</span><br><span class="line">0 0</span><br><span class="line">15 25</span><br></pre></td></tr></table></figure><p><strong>输出样例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">8</span><br><span class="line">30</span><br></pre></td></tr></table></figure><p><strong>代码实现</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Scanner;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Scanner</span> <span class="variable">sc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (sc.hasNextInt()) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> sc.nextInt();</span><br><span class="line">            <span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> sc.nextInt();</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (a == <span class="number">0</span> &amp;&amp; b == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">break</span>; <span class="comment">// 遇到0 0时结束</span></span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            System.out.println(a + b);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        sc.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>更多测试用例</strong>：</p><ul><li><p><strong>立即结束</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">0 0</span><br></pre></td></tr></table></figure><p>输出：（无输出）</p></li><li><p><strong>负数处理</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">-5 10</span><br><span class="line">3 -7</span><br><span class="line">0 0</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">5</span><br><span class="line">-4</span><br></pre></td></tr></table></figure></li><li><p><strong>单组数据</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">100 200</span><br><span class="line">0 0</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">300</span><br></pre></td></tr></table></figure></li></ul><h3 id="模式三：直到文件末尾">模式三：直到文件末尾</h3><p><strong>场景描述</strong>：<br>输入多组数据，直到文件末尾（EOF）。</p><p><strong>输入样例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1 2</span><br><span class="line">3 4</span><br><span class="line">5 6</span><br></pre></td></tr></table></figure><p><strong>输出样例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">3</span><br><span class="line">7</span><br><span class="line">11</span><br></pre></td></tr></table></figure><p><strong>代码实现</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Scanner;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Scanner</span> <span class="variable">sc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (sc.hasNextInt()) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> sc.nextInt();</span><br><span class="line">            <span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> sc.nextInt();</span><br><span class="line">            System.out.println(a + b);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        sc.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>更多测试用例</strong>：</p><ul><li><p><strong>单行输入</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">42 58</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">100</span><br></pre></td></tr></table></figure></li><li><p><strong>多行混合数据</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">10 20</span><br><span class="line">30 40</span><br><span class="line">50 60</span><br><span class="line">70 80</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">30</span><br><span class="line">70</span><br><span class="line">110</span><br><span class="line">150</span><br></pre></td></tr></table></figure></li><li><p><strong>实际应用场景</strong>（文件重定向）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 将数据保存在input.txt文件中</span></span><br><span class="line">java Main &lt; input.txt</span><br></pre></td></tr></table></figure></li></ul><h3 id="模式四：字符串处理">模式四：字符串处理</h3><p><strong>场景描述</strong>：<br>处理包含空格的字符串输入。</p><p><strong>输入样例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">3</span><br><span class="line">Hello world Java programming</span><br><span class="line">ACM algorithm competition</span><br><span class="line">Data structures and algorithms</span><br></pre></td></tr></table></figure><p><strong>输出样例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Word count: 4</span><br><span class="line">Word count: 3</span><br><span class="line">Word count: 4</span><br></pre></td></tr></table></figure><p><strong>代码实现</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Scanner;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Scanner</span> <span class="variable">sc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> sc.nextInt();</span><br><span class="line">        sc.nextLine(); <span class="comment">// 消费掉换行符</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">line</span> <span class="operator">=</span> sc.nextLine(); <span class="comment">// 读取整行</span></span><br><span class="line">            String[] words = line.split(<span class="string">&quot; &quot;</span>); <span class="comment">// 按空格分割</span></span><br><span class="line">            System.out.println(<span class="string">&quot;Word count: &quot;</span> + words.length);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        sc.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="⚠️-重要注意事项：Scanner的nextLine-陷阱">⚠️ 重要注意事项：Scanner的nextLine()陷阱</h3><p><strong>Q1: 为什么 nextLine() 在 nextInt() 或 next() 之后不生效？</strong></p><p><code>nextInt()</code> 或 <code>next()</code> <strong>不会读取换行符</strong>，导致后续的 <code>nextLine()</code> 直接读取到空行。</p><p><strong>解决方法</strong>：在 <code>nextInt()</code> 或 <code>next()</code> 后加一个 <code>nextLine()</code> 来消耗换行符。</p><p><strong>错误示例</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Scanner</span> <span class="variable">sc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line">System.out.print(<span class="string">&quot;输入数字：&quot;</span>);</span><br><span class="line"><span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> sc.nextInt();  <span class="comment">// 读取数字，但不读取换行符</span></span><br><span class="line">System.out.print(<span class="string">&quot;输入字符串：&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> sc.nextLine();  <span class="comment">// 直接读取换行符，导致 str 为空</span></span><br><span class="line">System.out.println(<span class="string">&quot;数字：&quot;</span> + num + <span class="string">&quot;，字符串：&quot;</span> + str);</span><br></pre></td></tr></table></figure><p><strong>输入</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">123</span><br><span class="line">Hello</span><br></pre></td></tr></table></figure><p><strong>输出</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">数字：123，字符串：</span><br></pre></td></tr></table></figure><p><code>nextLine()</code> 读取了 <code>nextInt()</code> 留下的换行符，导致 <code>str</code> 为空。</p><p><strong>修正方法</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Scanner</span> <span class="variable">sc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line">System.out.print(<span class="string">&quot;输入数字：&quot;</span>);</span><br><span class="line"><span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> sc.nextInt();</span><br><span class="line">sc.nextLine();  <span class="comment">// 消耗换行符</span></span><br><span class="line">System.out.print(<span class="string">&quot;输入字符串：&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> sc.nextLine();  <span class="comment">// 正确读取</span></span><br><span class="line">System.out.println(<span class="string">&quot;数字：&quot;</span> + num + <span class="string">&quot;，字符串：&quot;</span> + str);</span><br></pre></td></tr></table></figure><p><strong>输出</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">数字：123，字符串：Hello</span><br></pre></td></tr></table></figure><p><strong>更多测试用例</strong>：</p><ul><li><p><strong>多空格处理</strong>（改进版本）：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 改进版本：使用正则表达式处理多个空格</span></span><br><span class="line">String[] words = line.split(<span class="string">&quot;\\s+&quot;</span>);</span><br></pre></td></tr></table></figure></li><li><p><strong>空字符串测试</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">2</span><br><span class="line"></span><br><span class="line">Hello world</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Word count: 1</span><br><span class="line">Word count: 2</span><br></pre></td></tr></table></figure></li><li><p><strong>特殊字符处理</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">2</span><br><span class="line">Hello, world!</span><br><span class="line">Java-programming@2025</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Word count: 2</span><br><span class="line">Word count: 1</span><br></pre></td></tr></table></figure></li></ul><h3 id="模式五：高性能输入（BufferedReader）">模式五：高性能输入（BufferedReader）</h3><p><strong>场景描述</strong>：<br>处理大量数据时，需要更高效的输入方式。</p><p><strong>输入样例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">4</span><br><span class="line">100 200</span><br><span class="line">50 75</span><br><span class="line">300 400</span><br><span class="line">25 35</span><br></pre></td></tr></table></figure><p><strong>输出样例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">300</span><br><span class="line">125</span><br><span class="line">700</span><br><span class="line">60</span><br></pre></td></tr></table></figure><p><strong>代码实现</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.*;</span><br><span class="line"><span class="keyword">import</span> java.util.StringTokenizer;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="type">BufferedReader</span> <span class="variable">br</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BufferedReader</span>(<span class="keyword">new</span> <span class="title class_">InputStreamReader</span>(System.in));</span><br><span class="line">        <span class="type">StringTokenizer</span> <span class="variable">st</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringTokenizer</span>(br.readLine());</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> Integer.parseInt(st.nextToken());</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">            st = <span class="keyword">new</span> <span class="title class_">StringTokenizer</span>(br.readLine());</span><br><span class="line">            <span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> Integer.parseInt(st.nextToken());</span><br><span class="line">            <span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> Integer.parseInt(st.nextToken());</span><br><span class="line">            System.out.println(a + b);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        br.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>性能对比测试</strong>：</p><ul><li><strong>大数据量测试</strong>（10万行）：<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">100000</span><br><span class="line">1 2</span><br><span class="line">3 4</span><br><span class="line">5 6</span><br><span class="line">...</span><br><span class="line">[继续99997行]</span><br></pre></td></tr></table></figure><strong>性能数据</strong>：<ul><li>Scanner: 约500-800ms</li><li>BufferedReader: 约100-200ms</li><li><strong>性能提升</strong>：4-5倍</li></ul></li></ul><p><strong>适用场景</strong>：</p><ul><li>n &gt; 10000时推荐使用</li><li>时间限制严格的竞赛题目</li><li>需要处理大规模输入数据</li></ul><h3 id="模式六：二维数组输入">模式六：二维数组输入</h3><p><strong>场景描述</strong>：<br>输入二维数组或矩阵。</p><p><strong>输入样例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">3 4</span><br><span class="line">1 2 3 4</span><br><span class="line">5 6 7 8</span><br><span class="line">9 10 11 12</span><br></pre></td></tr></table></figure><p><strong>输出样例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Matrix sum: 78</span><br></pre></td></tr></table></figure><p><strong>代码实现</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Scanner;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Scanner</span> <span class="variable">sc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line">        <span class="type">int</span> <span class="variable">rows</span> <span class="operator">=</span> sc.nextInt();</span><br><span class="line">        <span class="type">int</span> <span class="variable">cols</span> <span class="operator">=</span> sc.nextInt();</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span>[][] matrix = <span class="keyword">new</span> <span class="title class_">int</span>[rows][cols];</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; rows; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; cols; j++) &#123;</span><br><span class="line">                matrix[i][j] = sc.nextInt();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 处理矩阵：计算所有元素的和</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; rows; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; cols; j++) &#123;</span><br><span class="line">                sum += matrix[i][j];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">&quot;Matrix sum: &quot;</span> + sum);</span><br><span class="line">        </span><br><span class="line">        sc.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>动态数组实现（使用ArrayList）</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DynamicMatrix</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">rows</span> <span class="operator">=</span> <span class="number">2</span>, cols = <span class="number">3</span>;</span><br><span class="line">        ArrayList&lt;ArrayList&lt;Integer&gt;&gt; matrix = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 为每行分配内存</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; rows; ++i) &#123;</span><br><span class="line">            ArrayList&lt;Integer&gt; row = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; cols; ++j) &#123;</span><br><span class="line">                row.add(<span class="number">0</span>);  <span class="comment">// 初始化为0</span></span><br><span class="line">            &#125;</span><br><span class="line">            matrix.add(row);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 初始化矩阵</span></span><br><span class="line">        matrix.get(<span class="number">0</span>).set(<span class="number">0</span>, <span class="number">1</span>);</span><br><span class="line">        matrix.get(<span class="number">0</span>).set(<span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line">        matrix.get(<span class="number">0</span>).set(<span class="number">2</span>, <span class="number">3</span>);</span><br><span class="line">        matrix.get(<span class="number">1</span>).set(<span class="number">0</span>, <span class="number">4</span>);</span><br><span class="line">        matrix.get(<span class="number">1</span>).set(<span class="number">1</span>, <span class="number">5</span>);</span><br><span class="line">        matrix.get(<span class="number">1</span>).set(<span class="number">2</span>, <span class="number">6</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 访问矩阵元素</span></span><br><span class="line">        System.out.println(matrix.get(<span class="number">1</span>).get(<span class="number">2</span>));  <span class="comment">// 输出6</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 输出矩阵的所有元素</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; matrix.size(); ++i) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; matrix.get(i).size(); ++j) &#123;</span><br><span class="line">                System.out.print(matrix.get(i).get(j) + <span class="string">&quot; &quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            System.out.println();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 释放内存（Java有垃圾回收机制，无需手动释放）</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>更多测试用例</strong>：</p><ul><li><p><strong>正方形矩阵</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">3 3</span><br><span class="line">1 2 3</span><br><span class="line">4 5 6</span><br><span class="line">7 8 9</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Matrix sum: 45</span><br></pre></td></tr></table></figure></li><li><p><strong>单行矩阵</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">1 5</span><br><span class="line">10 20 30 40 50</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Matrix sum: 150</span><br></pre></td></tr></table></figure></li><li><p><strong>单列矩阵</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">4 1</span><br><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Matrix sum: 10</span><br></pre></td></tr></table></figure></li><li><p><strong>边界情况</strong>（1×1矩阵）：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">1 1</span><br><span class="line">42</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Matrix sum: 42</span><br></pre></td></tr></table></figure></li></ul><h3 id="模式七：不定长数组输入">模式七：不定长数组输入</h3><p><strong>场景描述</strong>：<br>每行输入不定数量的数字。</p><p><strong>输入样例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">1 2 3 4 5</span><br><span class="line">10  20   30    40</span><br><span class="line">100</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>输出样例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">15</span><br><span class="line">100</span><br><span class="line">100</span><br></pre></td></tr></table></figure><p><strong>代码实现</strong>：</p><p><strong>方法1：使用 Scanner</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Scanner;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Scanner</span> <span class="variable">sc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (sc.hasNextLine()) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">line</span> <span class="operator">=</span> sc.nextLine();</span><br><span class="line">            <span class="keyword">if</span> (line.trim().isEmpty()) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            String[] numbers = line.split(<span class="string">&quot;\\s+&quot;</span>); <span class="comment">// 使用\\s+处理多个空格</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">for</span> (String num : numbers) &#123;</span><br><span class="line">                <span class="keyword">if</span> (!num.trim().isEmpty()) &#123;</span><br><span class="line">                    sum += Integer.parseInt(num);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            System.out.println(sum);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        sc.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>方法2：使用 Scanner嵌套</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Scanner;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Scanner</span> <span class="variable">sc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line">        String line;</span><br><span class="line">        <span class="type">long</span> sum;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (sc.hasNextLine()) &#123;  <span class="comment">// 持续读取每一行，直到输入结束</span></span><br><span class="line">            line = sc.nextLine();</span><br><span class="line">            <span class="keyword">if</span> (line.trim().isEmpty()) &#123;        <span class="comment">// 如果遇到空行，跳过</span></span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="type">Scanner</span> <span class="variable">lineScanner</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(line);  <span class="comment">// 创建新的Scanner读取每行的数据</span></span><br><span class="line">            sum = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">while</span> (lineScanner.hasNextLong()) &#123;        <span class="comment">// 逐个读取这一行的整数</span></span><br><span class="line">                <span class="type">long</span> <span class="variable">num</span> <span class="operator">=</span> lineScanner.nextLong();</span><br><span class="line">                sum += num;                            <span class="comment">// 将读取的整数累加到 sum 中</span></span><br><span class="line">            &#125;</span><br><span class="line">            System.out.println(sum);     <span class="comment">// 输出这一行所有整数的和</span></span><br><span class="line">            lineScanner.close();</span><br><span class="line">        &#125;</span><br><span class="line">        sc.close();                  <span class="comment">// 关闭Scanner</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>更多测试用例</strong>：</p><ul><li><p><strong>多空格处理</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">5   10    15      20</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">50</span><br></pre></td></tr></table></figure></li><li><p><strong>单行单数</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">42</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">42</span><br></pre></td></tr></table></figure></li><li><p><strong>负数处理</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-10 20 -5 15</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">20</span><br></pre></td></tr></table></figure></li><li><p><strong>空行结束</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1 2 3</span><br><span class="line">4 5 6</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">6</span><br><span class="line">15</span><br></pre></td></tr></table></figure></li></ul><h3 id="模式八：多组测试用例（每组不同格式）">模式八：多组测试用例（每组不同格式）</h3><p><strong>场景描述</strong>：<br>复杂的输入格式，每组测试用例可能包含不同类型的数据。</p><p><strong>输入样例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">3</span><br><span class="line">5</span><br><span class="line">1 2 3 4 5</span><br><span class="line">sum 0</span><br><span class="line">4</span><br><span class="line">10 20 30 40</span><br><span class="line">max 0</span><br><span class="line">6</span><br><span class="line">1 2 3 4 5 3</span><br><span class="line">count 3</span><br></pre></td></tr></table></figure><p><strong>输出样例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Case #1: 15</span><br><span class="line">Case #2: 40</span><br><span class="line">Case #3: 2</span><br></pre></td></tr></table></figure><p><strong>代码实现</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Scanner;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Scanner</span> <span class="variable">sc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line">        <span class="type">int</span> <span class="variable">t</span> <span class="operator">=</span> sc.nextInt(); <span class="comment">// 测试用例数量</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">caseNum</span> <span class="operator">=</span> <span class="number">1</span>; caseNum &lt;= t; caseNum++) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> sc.nextInt(); <span class="comment">// 数组大小</span></span><br><span class="line">            <span class="type">int</span>[] arr = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">                arr[i] = sc.nextInt();</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="type">String</span> <span class="variable">operation</span> <span class="operator">=</span> sc.next(); <span class="comment">// 操作类型</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">k</span> <span class="operator">=</span> sc.nextInt(); <span class="comment">// 操作参数</span></span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 执行操作</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">result</span> <span class="operator">=</span> processArray(arr, operation, k);</span><br><span class="line">            </span><br><span class="line">            System.out.println(<span class="string">&quot;Case #&quot;</span> + caseNum + <span class="string">&quot;: &quot;</span> + result);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        sc.close();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">processArray</span><span class="params">(<span class="type">int</span>[] arr, String operation, <span class="type">int</span> k)</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span> (operation.toLowerCase()) &#123;</span><br><span class="line">            <span class="keyword">case</span> <span class="string">&quot;sum&quot;</span>:</span><br><span class="line">                <span class="keyword">return</span> java.util.Arrays.stream(arr).sum();</span><br><span class="line">            <span class="keyword">case</span> <span class="string">&quot;max&quot;</span>:</span><br><span class="line">                <span class="keyword">return</span> java.util.Arrays.stream(arr).max().orElse(<span class="number">0</span>);</span><br><span class="line">            <span class="keyword">case</span> <span class="string">&quot;count&quot;</span>:</span><br><span class="line">                <span class="keyword">return</span> (<span class="type">int</span>) java.util.Arrays.stream(arr).filter(x -&gt; x == k).count();</span><br><span class="line">            <span class="keyword">case</span> <span class="string">&quot;find&quot;</span>:</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; arr.length; i++) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (arr[i] == k) <span class="keyword">return</span> i;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">            <span class="keyword">default</span>:</span><br><span class="line">                <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>更多测试用例</strong>：</p><ul><li><p><strong>查找操作</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">2</span><br><span class="line">5</span><br><span class="line">10 20 30 40 50</span><br><span class="line">find 30</span><br><span class="line">3</span><br><span class="line">1 2 3</span><br><span class="line">find 5</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Case #1: 2</span><br><span class="line">Case #2: -1</span><br></pre></td></tr></table></figure></li><li><p><strong>单元素数组</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">1</span><br><span class="line">1</span><br><span class="line">42</span><br><span class="line">sum 0</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Case #1: 42</span><br></pre></td></tr></table></figure></li><li><p><strong>边界测试</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">1</span><br><span class="line">3</span><br><span class="line">-5 0 5</span><br><span class="line">max 0</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Case #1: 5</span><br></pre></td></tr></table></figure></li></ul><h2 id="🚀-关键要点">🚀 关键要点</h2><h3 id="核心概念">核心概念</h3><ol><li><strong>输入流处理</strong>：理解如何读取不同类型的输入数据</li><li><strong>循环控制</strong>：掌握各种输入结束条件的处理</li><li><strong>字符串分割</strong>：熟练使用split()和StringTokenizer</li><li><strong>异常处理</strong>：合理处理输入异常</li></ol><h3 id="常见陷阱">常见陷阱</h3><ol><li><strong>换行符处理</strong>：nextInt()后使用nextLine()需要特别注意</li><li><strong>空格处理</strong>：字符串输入中的空格分割问题</li><li><strong>EOF判断</strong>：正确处理文件结束条件</li><li><strong>性能问题</strong>：大量数据时选择合适的输入方式</li></ol><h3 id="性能优化建议">性能优化建议</h3><ul><li>小数据量：使用Scanner，代码简洁</li><li>大数据量：使用BufferedReader + StringTokenizer</li><li>超大数组：考虑使用快速输入输出类</li></ul><h2 id="📚-实战练习">📚 实战练习</h2><h3 id="练习题1：A-B-Problem">练习题1：A + B Problem</h3><p><strong>描述</strong>：计算两个数的和，直到文件结束。<br><strong>输入</strong>：多行，每行两个整数<br><strong>输出</strong>：每行输出对应的和</p><h3 id="练习题2：字符串统计">练习题2：字符串统计</h3><p><strong>描述</strong>：统计输入字符串中各个字符的出现次数。<br><strong>输入</strong>：第一行是测试用例数量，后面是字符串<br><strong>输出</strong>：每个字符串的字符统计结果</p><h3 id="练习题3：矩阵运算">练习题3：矩阵运算</h3><p><strong>描述</strong>：实现矩阵的转置操作。<br><strong>输入</strong>：矩阵的行数和列数，然后是矩阵元素<br><strong>输出</strong>：转置后的矩阵</p><h2 id="🏷️-相关标签">🏷️ 相关标签</h2><ul><li>ACM</li><li>Java</li><li>输入输出</li><li>算法竞赛</li><li>编程技巧</li><li>笔试面试</li></ul><h2 id="📖-参考资料">📖 参考资料</h2><ul><li><a href="https://docs.oracle.com/javase/8/docs/api/java/util/Scanner.html">Java Scanner类文档</a></li><li><a href="https://book.douban.com/subject/19952400/">算法竞赛入门经典</a></li><li><a href="https://example.com/oj-guide">在线判题系统使用指南</a></li></ul><hr><p><em>最后更新：2025-01-15</em></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;ACM模式Java输入输出处理完全指南&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;难度&lt;/strong&gt;: 基础到进阶&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;标签&lt;/strong&gt;: ACM, Java, 输入输出, 算法竞赛, 编程技巧&lt;/p&gt;
&lt;p&gt;&lt;stro</summary>
      
    
    
    
    <category term="书山指路" scheme="https://blog.adoreorg.cn/categories/%E4%B9%A6%E5%B1%B1%E6%8C%87%E8%B7%AF/"/>
    
    
    <category term="算法" scheme="https://blog.adoreorg.cn/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="ACM" scheme="https://blog.adoreorg.cn/tags/ACM/"/>
    
    <category term="Java" scheme="https://blog.adoreorg.cn/tags/Java/"/>
    
    <category term="输入输出" scheme="https://blog.adoreorg.cn/tags/%E8%BE%93%E5%85%A5%E8%BE%93%E5%87%BA/"/>
    
    <category term="编程技巧" scheme="https://blog.adoreorg.cn/tags/%E7%BC%96%E7%A8%8B%E6%8A%80%E5%B7%A7/"/>
    
  </entry>
  
  <entry>
    <title>动态规划算法详解：从入门到精通</title>
    <link href="https://blog.adoreorg.cn/posts/ds34d27g.html"/>
    <id>https://blog.adoreorg.cn/posts/ds34d27g.html</id>
    <published>2025-01-14T16:00:00.000Z</published>
    <updated>2025-10-07T15:41:58.253Z</updated>
    
    <content type="html"><![CDATA[<h1>🦋 动态规划详解：从入门到精通</h1><blockquote><p>✨ <strong>一文掌握动态规划精髓</strong>：状态定义 + 状态转移 + 最优子结构 = 高效解题</p><p>🎯 <strong>核心思想</strong>：将复杂问题分解为重叠子问题，存储子问题的解避免重复计算</p><p>📚 <strong>适用场景</strong>：最优化问题、计数问题、存在性问题等</p></blockquote><h2 id="📖-目录">📖 目录</h2><ul><li><a href="#%E4%BB%80%E4%B9%88%E6%98%AF%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92">什么是动态规划</a></li><li><a href="#%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%A1%86%E6%9E%B6">动态规划的基本框架</a></li><li><a href="#%E8%A7%A3%E9%A2%98%E5%9B%9B%E6%AD%A5%E6%B3%95%E7%B3%BB%E7%BB%9F%E6%94%BB%E5%85%8Bdp%E9%97%AE%E9%A2%98">解题四步法：系统攻克DP问题</a></li><li><a href="#%E7%BB%8F%E5%85%B8%E9%97%AE%E9%A2%98%E8%AF%A6%E8%A7%A3">经典问题详解</a></li><li><a href="#%E4%BC%98%E5%8C%96%E6%8A%80%E5%B7%A7%E4%B8%8E%E9%AB%98%E7%BA%A7%E7%AD%96%E7%95%A5">优化技巧与高级策略</a></li><li><a href="#%E5%AE%9E%E9%99%85%E5%BA%94%E7%94%A8%E6%A1%88%E4%BE%8B">实际应用案例</a></li><li><a href="#%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90">性能分析</a></li><li><a href="#%E5%AE%9E%E6%88%98%E7%BB%83%E4%B9%A0">实战练习</a></li><li><a href="#%E7%9B%B8%E5%85%B3leetcode%E9%A2%98%E7%9B%AE">相关LeetCode题目</a></li></ul><hr><h2 id="🎯-什么是动态规划">🎯 什么是动态规划</h2><p>动态规划（Dynamic Programming，DP）是一种通过将复杂问题分解为更小的重叠子问题来求解的方法。与分治法不同，动态规划会存储子问题的解，避免重复计算。</p><h3 id="核心思想">核心思想</h3><ul><li><strong>最优子结构</strong>：问题的最优解包含子问题的最优解</li><li><strong>重叠子问题</strong>：问题可以分解为重复出现的子问题</li><li><strong>状态存储</strong>：用表格存储子问题的解，避免重复计算</li><li><strong>无后效性</strong>：未来的决策不影响过去的状态</li></ul><h3 id="适用场景">适用场景</h3><ul><li><strong>最优化问题</strong>：求最大值、最小值、最优方案等</li><li><strong>计数问题</strong>：求方案数、路径数、组合数等</li><li><strong>存在性问题</strong>：判断是否存在某种方案</li><li><strong>字符串问题</strong>：编辑距离、最长公共子序列等</li><li><strong>背包问题</strong>：0-1背包、完全背包、多重背包等</li></ul><hr><h2 id="🚀-快速入门指南">🚀 快速入门指南</h2><h3 id="🎯-什么是动态规划？">🎯 什么是动态规划？</h3><p>动态规划 = <strong>分治 + 记忆化 + 最优子结构</strong></p><p>想象你在爬楼梯：</p><ul><li>🏃‍♂️ <strong>第1步</strong>：到达第n阶的方法 = 到达第(n-1)阶的方法 + 到达第(n-2)阶的方法</li><li>💾 <strong>记忆化</strong>：记录到达每一阶的方法数，避免重复计算</li><li>🎯 <strong>最优解</strong>：选择到达终点的最优路径</li></ul><h3 id="💡-核心四要素">💡 核心四要素</h3><table><thead><tr><th>要素</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td><strong>状态定义</strong></td><td>描述子问题的变量</td><td><code>dp[i]</code> 表示到达第i阶的方法数</td></tr><tr><td><strong>状态转移</strong></td><td>状态之间的关系</td><td><code>dp[i] = dp[i-1] + dp[i-2]</code></td></tr><tr><td><strong>初始条件</strong></td><td>最小子问题的解</td><td><code>dp[0] = 1, dp[1] = 1</code></td></tr><tr><td><strong>最终结果</strong></td><td>原问题的解</td><td><code>dp[n]</code></td></tr></tbody></table><h3 id="🔑-万能模板（记住这个就够了！）">🔑 万能模板（记住这个就够了！）</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 🎯 步骤1：状态定义</span></span><br><span class="line">dp[i] 表示...（明确状态的含义）</span><br><span class="line"></span><br><span class="line"><span class="comment">// 🚀 步骤2：状态转移方程</span></span><br><span class="line">dp[i] = f(dp[i-<span class="number">1</span>], dp[i-<span class="number">2</span>], ...) <span class="comment">// 根据问题特点确定</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 📝 步骤3：初始条件</span></span><br><span class="line">dp[<span class="number">0</span>] = ... <span class="comment">// 最小子问题的解</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 🎨 步骤4：计算顺序</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">    dp[i] = ... <span class="comment">// 按状态转移方程计算</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 步骤5：返回结果</span></span><br><span class="line"><span class="keyword">return</span> dp[n]; <span class="comment">// 或其他需要的结果</span></span><br></pre></td></tr></table></figure><h3 id="🎨-举个简单例子：爬楼梯问题">🎨 举个简单例子：爬楼梯问题</h3><p>假设你正在爬楼梯。需要n阶你才能到达楼顶。每次你可以爬1或2个台阶。你有多少种不同的方法可以爬到楼顶呢？</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 爬楼梯问题 - 动态规划解法</span></span><br><span class="line"><span class="comment"> * 📝 问题：每次可以爬1或2阶，求到达第n阶的方法数</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> n 楼梯总阶数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 到达楼顶的方法数</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">climbStairs</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="comment">// 🛡️ 边界条件处理</span></span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">2</span>) <span class="keyword">return</span> n;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 📝 步骤1：状态定义</span></span><br><span class="line">    <span class="comment">// dp[i] 表示到达第i阶楼梯的方法数</span></span><br><span class="line">    <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[n + <span class="number">1</span>];</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 📝 步骤2：初始条件</span></span><br><span class="line">    dp[<span class="number">0</span>] = <span class="number">1</span>;  <span class="comment">// 地面有1种方法（不爬）</span></span><br><span class="line">    dp[<span class="number">1</span>] = <span class="number">1</span>;  <span class="comment">// 第1阶有1种方法</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 🚀 步骤3：状态转移</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="comment">// 到达第i阶的方法 = 从i-1阶爬1阶 + 从i-2阶爬2阶</span></span><br><span class="line">        dp[i] = dp[i-<span class="number">1</span>] + dp[i-<span class="number">2</span>];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// ✅ 步骤4：返回结果</span></span><br><span class="line">    <span class="keyword">return</span> dp[n];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 空间优化版本 - 滚动数组</span></span><br><span class="line"><span class="comment"> * 💡 观察到只需要前两个状态，可以将空间复杂度优化到O(1)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">climbStairsOptimized</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">2</span>) <span class="keyword">return</span> n;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">prev2</span> <span class="operator">=</span> <span class="number">1</span>;  <span class="comment">// dp[i-2]</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">prev1</span> <span class="operator">=</span> <span class="number">1</span>;  <span class="comment">// dp[i-1]</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">current</span> <span class="operator">=</span> prev1 + prev2;  <span class="comment">// dp[i] = dp[i-1] + dp[i-2]</span></span><br><span class="line">        prev2 = prev1;  <span class="comment">// 更新dp[i-2]</span></span><br><span class="line">        prev1 = current; <span class="comment">// 更新dp[i-1]</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> prev1;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="⚡-一分钟理解动态规划">⚡ 一分钟理解动态规划</h3><ol><li><strong>定义状态</strong>：找到能描述子问题的变量 📊</li><li><strong>找出转移</strong>：发现状态之间的关系 🔄</li><li><strong>确定初始</strong>：设置最小子问题的解 🎯</li><li><strong>计算结果</strong>：按顺序计算所有状态 ✅</li></ol><p><strong>关键</strong>：<strong>状态定义</strong>和<strong>状态转移方程</strong>是核心！</p><hr><h2 id="🏗️-动态规划的基本框架">🏗️ 动态规划的基本框架</h2><h3 id="通用模板">通用模板</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DynamicProgrammingTemplate</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">solveDP</span><span class="params">(<span class="type">int</span>[] params)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 参数校验和边界处理</span></span><br><span class="line">        <span class="keyword">if</span> (params == <span class="literal">null</span> || params.length == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> params.length;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 状态定义（根据问题复杂度选择一维、二维或多维）</span></span><br><span class="line">        <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[n + <span class="number">1</span>];           <span class="comment">// 一维DP</span></span><br><span class="line">        <span class="comment">// int[][] dp = new int[n + 1][m + 1]; // 二维DP</span></span><br><span class="line">        <span class="comment">// int[][][] dp = new int[n + 1][m + 1][k + 1]; // 三维DP</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 初始条件设置</span></span><br><span class="line">        dp[<span class="number">0</span>] = ...; <span class="comment">// 最小子问题的解</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 状态转移计算</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">            <span class="comment">// 根据状态转移方程计算dp[i]</span></span><br><span class="line">            dp[i] = ...;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 可能需要考虑多种选择</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; i; j++) &#123;</span><br><span class="line">                dp[i] = Math.max(dp[i], dp[j] + ...);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 5. 返回最终结果</span></span><br><span class="line">        <span class="keyword">return</span> dp[n];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="框架解析">框架解析</h3><ol><li><strong>状态定义</strong>：确定dp数组的维度和含义</li><li><strong>初始条件</strong>：设置最小规模子问题的解</li><li><strong>状态转移</strong>：建立状态之间的关系方程</li><li><strong>计算顺序</strong>：确定状态的计算顺序（通常从小到大）</li><li><strong>结果提取</strong>：从dp数组中提取最终答案</li></ol><hr><h2 id="🎯-解题四步法：系统攻克DP问题">🎯 解题四步法：系统攻克DP问题</h2><h3 id="📝-步骤1：问题分析">📝 步骤1：问题分析</h3><p><strong>🔍 关键问题</strong>：</p><ul><li>❓ <strong>最优子结构？</strong> 问题能否分解为子问题？</li><li>❓ <strong>重叠子问题？</strong> 子问题是否重复出现？</li><li>❓ <strong>无后效性？</strong> 未来决策是否影响过去？</li><li>❓ <strong>状态如何定义？</strong> 用什么变量描述子问题？</li></ul><p><strong>🎨 示例：最长递增子序列</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">问题：求数组中最长的严格递增子序列长度</span><br><span class="line">状态：dp[i] 表示以第i个元素结尾的最长递增子序列长度</span><br><span class="line">转移：dp[i] = max(dp[j] + 1) for all j &lt; i &amp;&amp; nums[j] &lt; nums[i]</span><br><span class="line">初始：dp[i] = 1 (每个元素本身就是一个长度为1的递增子序列)</span><br></pre></td></tr></table></figure><h3 id="🛠️-步骤2：状态定义与转移">🛠️ 步骤2：状态定义与转移</h3><p><strong>🎯 状态定义技巧</strong>：</p><ul><li><strong>一维状态</strong>：<code>dp[i]</code> 表示前i个元素的某种性质</li><li><strong>二维状态</strong>：<code>dp[i][j]</code> 表示前i个元素在状态j下的某种性质</li><li><strong>多维状态</strong>：根据问题复杂度增加维度</li></ul><p><strong>🧩 状态转移方程</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 🎯 最值型：求最大值或最小值</span></span><br><span class="line">dp[i] = max/min(dp[i-<span class="number">1</span>], dp[i-<span class="number">2</span>], ...) + cost</span><br><span class="line"></span><br><span class="line"><span class="comment">// 🎯 计数型：求方案数</span></span><br><span class="line">dp[i] = sum(dp[i-<span class="number">1</span>], dp[i-<span class="number">2</span>], ...)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 🎯 存在型：判断是否存在</span></span><br><span class="line">dp[i] = dp[i-<span class="number">1</span>] || dp[i-<span class="number">2</span>] || ...</span><br></pre></td></tr></table></figure><h3 id="⚡-步骤3：优化策略">⚡ 步骤3：优化策略</h3><p><strong>💡 空间优化</strong>：</p><ul><li><strong>滚动数组</strong>：只保留必要的前几个状态</li><li><strong>状态压缩</strong>：用位运算表示状态</li><li><strong>降维打击</strong>：减少dp数组的维度</li></ul><p><strong>🔪 时间优化</strong>：</p><ul><li><strong>前缀和/差分</strong>：快速计算区间和</li><li><strong>单调队列</strong>：维护滑动窗口最值</li><li><strong>线段树/树状数组</strong>：高效区间查询</li></ul><h3 id="🧪-步骤4：实现与验证">🧪 步骤4：实现与验证</h3><p><strong>✅ 实现要点</strong>：</p><ul><li><strong>边界条件</strong>：处理空数组、单元素等特殊情况</li><li><strong>循环顺序</strong>：确保计算dp[i]时所需的前置状态已计算</li><li><strong>状态初始化</strong>：正确设置dp数组的初始值</li></ul><p><strong>🎯 验证方法</strong>：</p><ul><li><strong>小规模验证</strong>：用简单例子验证状态转移</li><li><strong>打印dp数组</strong>：调试时输出中间状态</li><li><strong>复杂度分析</strong>：时间复杂度 = 状态数 × 转移复杂度</li></ul><hr><h2 id="🧩-经典问题详解：从理论到实战">🧩 经典问题详解：从理论到实战</h2><blockquote><p>🎯 <strong>学习目标</strong>：通过5个经典问题，掌握动态规划的核心技巧</p><p>📊 <strong>难度梯度</strong>：⭐☆☆☆ → ⭐⭐☆☆ → ⭐⭐⭐☆ → ⭐⭐⭐⭐ → ⭐⭐⭐⭐⭐</p><p>💡 <strong>核心技巧</strong>：状态定义 + 转移方程 + 空间优化</p></blockquote><h3 id="1-斐波那契数列-⭐☆☆☆">1. 斐波那契数列 ⭐☆☆☆</h3><p><strong>🎯 LeetCode 509</strong>：求第n个斐波那契数</p><p><strong>💡 核心思路</strong>：经典入门问题，展示DP基本思想</p><p><strong>🎨 状态定义</strong>：</p><ul><li><code>dp[i]</code> 表示第i个斐波那契数</li><li>转移方程：<code>dp[i] = dp[i-1] + dp[i-2]</code></li><li>初始条件：<code>dp[0] = 0, dp[1] = 1</code></li></ul><h4 id="基础实现">基础实现</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Fibonacci</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🎯 斐波那契数列 - 动态规划解法</span></span><br><span class="line"><span class="comment">     * 📝 状态：dp[i] 表示第i个斐波那契数</span></span><br><span class="line"><span class="comment">     * 📝 转移：dp[i] = dp[i-1] + dp[i-2]</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">fib</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">        <span class="comment">// 🛡️ 边界条件</span></span><br><span class="line">        <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) <span class="keyword">return</span> n;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 状态定义</span></span><br><span class="line">        <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[n + <span class="number">1</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 初始条件</span></span><br><span class="line">        dp[<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line">        dp[<span class="number">1</span>] = <span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 状态转移</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">            dp[i] = dp[i-<span class="number">1</span>] + dp[i-<span class="number">2</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> dp[n];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="空间优化版本">空间优化版本</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 空间优化版本 - 滚动数组</span></span><br><span class="line"><span class="comment"> * 💡 只需要前两个状态，空间复杂度O(1)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">fibOptimized</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) <span class="keyword">return</span> n;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">prev2</span> <span class="operator">=</span> <span class="number">0</span>;  <span class="comment">// dp[i-2]</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">prev1</span> <span class="operator">=</span> <span class="number">1</span>;  <span class="comment">// dp[i-1]</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">current</span> <span class="operator">=</span> prev1 + prev2;</span><br><span class="line">        prev2 = prev1;</span><br><span class="line">        prev1 = current;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> prev1;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-零钱兑换-⭐⭐☆☆">2. 零钱兑换 ⭐⭐☆☆</h3><p><strong>🎯 LeetCode 322</strong>：给定不同面额的硬币和一个总金额，求最少需要多少枚硬币</p><p><strong>💡 核心思路</strong>：完全背包问题的变种，求最少物品数</p><p><strong>🎨 状态定义</strong>：</p><ul><li><code>dp[i]</code> 表示凑齐金额i所需的最少硬币数</li><li>转移方程：<code>dp[i] = min(dp[i], dp[i-coin] + 1)</code> for each coin</li><li>初始条件：<code>dp[0] = 0</code>，其他为无穷大</li></ul><h4 id="完整实现">完整实现</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CoinChange</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🎯 零钱兑换 - 最少硬币数</span></span><br><span class="line"><span class="comment">     * 📝 状态：dp[i] 表示凑齐金额i的最少硬币数</span></span><br><span class="line"><span class="comment">     * 📝 转移：dp[i] = min(dp[i], dp[i-coin] + 1)</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">coinChange</span><span class="params">(<span class="type">int</span>[] coins, <span class="type">int</span> amount)</span> &#123;</span><br><span class="line">        <span class="comment">// 📝 步骤1：状态定义</span></span><br><span class="line">        <span class="comment">// dp[i] 表示凑齐金额i所需的最少硬币数</span></span><br><span class="line">        <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[amount + <span class="number">1</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 步骤2：初始条件</span></span><br><span class="line">        <span class="comment">// dp[0] = 0，其他设置为无穷大（表示不可达）</span></span><br><span class="line">        Arrays.fill(dp, amount + <span class="number">1</span>);  <span class="comment">// 用amount+1表示无穷大</span></span><br><span class="line">        dp[<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 步骤3：状态转移</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= amount; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> coin : coins) &#123;</span><br><span class="line">                <span class="keyword">if</span> (coin &lt;= i) &#123;  <span class="comment">// 硬币面额不超过当前金额</span></span><br><span class="line">                    dp[i] = Math.min(dp[i], dp[i - coin] + <span class="number">1</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ✅ 步骤4：返回结果</span></span><br><span class="line">        <span class="keyword">return</span> dp[amount] &gt; amount ? -<span class="number">1</span> : dp[amount];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🎯 零钱兑换 - 求所有方案数</span></span><br><span class="line"><span class="comment">     * 💡 将min换成sum，求方案总数</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">coinChangeWays</span><span class="params">(<span class="type">int</span>[] coins, <span class="type">int</span> amount)</span> &#123;</span><br><span class="line">        <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[amount + <span class="number">1</span>];</span><br><span class="line">        dp[<span class="number">0</span>] = <span class="number">1</span>;  <span class="comment">// 凑齐0元有1种方法（不选任何硬币）</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= amount; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> coin : coins) &#123;</span><br><span class="line">                <span class="keyword">if</span> (coin &lt;= i) &#123;</span><br><span class="line">                    dp[i] += dp[i - coin];</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> dp[amount];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-最长递增子序列-⭐⭐⭐☆">3. 最长递增子序列 ⭐⭐⭐☆</h3><p><strong>🎯 LeetCode 300</strong>：求数组中最长的严格递增子序列长度</p><p><strong>💡 核心思路</strong>：序列DP，需要比较前面所有状态</p><p><strong>🎨 状态定义</strong>：</p><ul><li><code>dp[i]</code> 表示以第i个元素结尾的最长递增子序列长度</li><li>转移方程：<code>dp[i] = max(dp[j] + 1)</code> for all <code>j &lt; i &amp;&amp; nums[j] &lt; nums[i]</code></li><li>初始条件：<code>dp[i] = 1</code> (每个元素本身就是一个长度为1的序列)</li></ul><h4 id="完整实现-2">完整实现</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LongestIncreasingSubsequence</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🎯 最长递增子序列 - 动态规划解法</span></span><br><span class="line"><span class="comment">     * 📝 状态：dp[i] 表示以第i个元素结尾的最长递增子序列长度</span></span><br><span class="line"><span class="comment">     * 📝 转移：dp[i] = max(dp[j] + 1) for all j &lt; i &amp;&amp; nums[j] &lt; nums[i]</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">lengthOfLIS</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (nums == <span class="literal">null</span> || nums.length == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> nums.length;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 状态定义</span></span><br><span class="line">        <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 初始条件：每个元素本身就是一个长度为1的序列</span></span><br><span class="line">        Arrays.fill(dp, <span class="number">1</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> <span class="variable">maxLength</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 状态转移</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; n; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; i; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (nums[j] &lt; nums[i]) &#123;  <span class="comment">// 满足递增条件</span></span><br><span class="line">                    dp[i] = Math.max(dp[i], dp[j] + <span class="number">1</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            maxLength = Math.max(maxLength, dp[i]);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> maxLength;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🎯 贪心 + 二分查找优化 - O(nlogn)</span></span><br><span class="line"><span class="comment">     * 💡 维护一个递增序列，用二分查找优化</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">lengthOfLISOptimized</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (nums == <span class="literal">null</span> || nums.length == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        List&lt;Integer&gt; tails = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> num : nums) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = tails.size();</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 二分查找插入位置</span></span><br><span class="line">            <span class="keyword">while</span> (left &lt; right) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">                <span class="keyword">if</span> (tails.get(mid) &lt; num) &#123;</span><br><span class="line">                    left = mid + <span class="number">1</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    right = mid;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (left == tails.size()) &#123;</span><br><span class="line">                tails.add(num);  <span class="comment">// 比所有元素都大，扩展序列</span></span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                tails.set(left, num);  <span class="comment">// 替换，维护最小尾部元素</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> tails.size();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-编辑距离-⭐⭐⭐⭐">4. 编辑距离 ⭐⭐⭐⭐</h3><p><strong>🎯 LeetCode 72</strong>：求两个字符串之间的最小编辑距离（插入、删除、替换）</p><p><strong>💡 核心思路</strong>：二维DP，两个字符串的匹配问题</p><p><strong>🎨 状态定义</strong>：</p><ul><li><code>dp[i][j]</code> 表示word1的前i个字符和word2的前j个字符之间的最小编辑距离</li><li>转移方程：<ul><li>如果字符相等：<code>dp[i][j] = dp[i-1][j-1]</code></li><li>如果字符不等：<code>dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1</code></li></ul></li><li>初始条件：<code>dp[i][0] = i</code>，<code>dp[0][j] = j</code></li></ul><h4 id="完整实现-3">完整实现</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EditDistance</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🎯 编辑距离 - 动态规划解法</span></span><br><span class="line"><span class="comment">     * 📝 状态：dp[i][j] 表示word1前i字符和word2前j字符的最小编辑距离</span></span><br><span class="line"><span class="comment">     * 📝 操作：插入、删除、替换</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">minDistance</span><span class="params">(String word1, String word2)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">m</span> <span class="operator">=</span> word1.length();</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> word2.length();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 状态定义</span></span><br><span class="line">        <span class="type">int</span>[][] dp = <span class="keyword">new</span> <span class="title class_">int</span>[m + <span class="number">1</span>][n + <span class="number">1</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 初始条件</span></span><br><span class="line">        <span class="comment">// word1前i字符变成空字符串需要i次删除</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt;= m; i++) &#123;</span><br><span class="line">            dp[i][<span class="number">0</span>] = i;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 空字符串变成word2前j字符需要j次插入</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt;= n; j++) &#123;</span><br><span class="line">            dp[<span class="number">0</span>][j] = j;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 状态转移</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt;= n; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (word1.charAt(i-<span class="number">1</span>) == word2.charAt(j-<span class="number">1</span>)) &#123;</span><br><span class="line">                    <span class="comment">// 字符相等，不需要操作</span></span><br><span class="line">                    dp[i][j] = dp[i-<span class="number">1</span>][j-<span class="number">1</span>];</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="comment">// 字符不等，取三种操作的最小值</span></span><br><span class="line">                    <span class="type">int</span> <span class="variable">replace</span> <span class="operator">=</span> dp[i-<span class="number">1</span>][j-<span class="number">1</span>] + <span class="number">1</span>;  <span class="comment">// 替换</span></span><br><span class="line">                    <span class="type">int</span> <span class="variable">delete</span> <span class="operator">=</span> dp[i-<span class="number">1</span>][j] + <span class="number">1</span>;   <span class="comment">// 删除</span></span><br><span class="line">                    <span class="type">int</span> <span class="variable">insert</span> <span class="operator">=</span> dp[i][j-<span class="number">1</span>] + <span class="number">1</span>;   <span class="comment">// 插入</span></span><br><span class="line">                    dp[i][j] = Math.min(Math.min(replace, delete), insert);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> dp[m][n];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-正则表达式匹配-⭐⭐⭐⭐⭐">5. 正则表达式匹配 ⭐⭐⭐⭐⭐</h3><p><strong>🎯 LeetCode 10</strong>：实现正则表达式匹配，支持<code>.</code>和<code>*</code></p><p><strong>💡 核心思路</strong>：二维DP，处理通配符匹配的复杂情况</p><p><strong>🎨 状态定义</strong>：</p><ul><li><code>dp[i][j]</code> 表示s的前i个字符和p的前j个字符是否匹配</li><li>转移方程：<ul><li>如果<code>p[j-1]</code>不是<code>*</code>：直接比较字符</li><li>如果<code>p[j-1]</code>是<code>*</code>：考虑匹配0个或多个前驱字符</li></ul></li><li>初始条件：<code>dp[0][0] = true</code>，处理空字符串匹配</li></ul><h4 id="完整实现-4">完整实现</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RegularExpressionMatching</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🎯 正则表达式匹配 - 动态规划解法</span></span><br><span class="line"><span class="comment">     * 📝 状态：dp[i][j] 表示s前i字符和p前j字符是否匹配</span></span><br><span class="line"><span class="comment">     * 📝 通配符：. 匹配任意字符，* 匹配前面字符0次或多次</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isMatch</span><span class="params">(String s, String p)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">m</span> <span class="operator">=</span> s.length();</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> p.length();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 状态定义</span></span><br><span class="line">        <span class="type">boolean</span>[][] dp = <span class="keyword">new</span> <span class="title class_">boolean</span>[m + <span class="number">1</span>][n + <span class="number">1</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 初始条件：两个空字符串匹配</span></span><br><span class="line">        dp[<span class="number">0</span>][<span class="number">0</span>] = <span class="literal">true</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 处理模式中的*，可以匹配空字符串</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt;= n; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (p.charAt(j-<span class="number">1</span>) == <span class="string">&#x27;*&#x27;</span>) &#123;</span><br><span class="line">                dp[<span class="number">0</span>][j] = dp[<span class="number">0</span>][j-<span class="number">2</span>];  <span class="comment">// *匹配0个前驱字符</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 状态转移</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt;= n; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (p.charAt(j-<span class="number">1</span>) == <span class="string">&#x27;*&#x27;</span>) &#123;</span><br><span class="line">                    <span class="comment">// 处理*通配符</span></span><br><span class="line">                    <span class="type">char</span> <span class="variable">prevChar</span> <span class="operator">=</span> p.charAt(j-<span class="number">2</span>);</span><br><span class="line">                    <span class="comment">// *匹配0个前驱字符</span></span><br><span class="line">                    dp[i][j] = dp[i][j-<span class="number">2</span>];</span><br><span class="line">                    <span class="comment">// *匹配1个或多个前驱字符</span></span><br><span class="line">                    <span class="keyword">if</span> (prevChar == <span class="string">&#x27;.&#x27;</span> || prevChar == s.charAt(i-<span class="number">1</span>)) &#123;</span><br><span class="line">                        dp[i][j] = dp[i][j] || dp[i-<span class="number">1</span>][j];</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (p.charAt(j-<span class="number">1</span>) == <span class="string">&#x27;.&#x27;</span>) &#123;</span><br><span class="line">                    <span class="comment">// 处理.通配符</span></span><br><span class="line">                    dp[i][j] = dp[i-<span class="number">1</span>][j-<span class="number">1</span>];</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="comment">// 普通字符匹配</span></span><br><span class="line">                    dp[i][j] = dp[i-<span class="number">1</span>][j-<span class="number">1</span>] &amp;&amp; (s.charAt(i-<span class="number">1</span>) == p.charAt(j-<span class="number">1</span>));</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> dp[m][n];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-0-1背包问题-⭐⭐⭐⭐⭐">6. 0/1背包问题 ⭐⭐⭐⭐⭐</h3><p><strong>🎯 经典问题</strong>：有N件物品和一个容量为W的背包，第i件物品的重量是w[i]，价值是v[i]，求解将哪些物品装入背包可使这些物品的总重量不超过背包容量，且价值总和最大。</p><p><strong>💡 核心思路</strong>：二维DP，每个物品只能选择一次（0/1选择）</p><p><strong>🎨 状态定义</strong>：</p><ul><li><code>dp[i][j]</code> 表示前i件物品放入容量为j的背包中的最大价值</li><li>转移方程：<ul><li>不选第i件物品：<code>dp[i][j] = dp[i-1][j]</code></li><li>选第i件物品：<code>dp[i][j] = dp[i-1][j-w[i-1]] + v[i-1]</code>（前提是j ≥ w[i-1]）</li></ul></li><li>初始条件：<code>dp[0][j] = 0</code>（0件物品价值为0）</li></ul><h4 id="完整实现-5">完整实现</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">KnapsackProblem</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🎯 0/1背包问题 - 动态规划解法</span></span><br><span class="line"><span class="comment">     * 📝 状态：dp[i][j] 表示前i件物品放入容量为j的背包中的最大价值</span></span><br><span class="line"><span class="comment">     * 📝 选择：对于每件物品，可以选择放入或不放入背包</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">knapsack</span><span class="params">(<span class="type">int</span> W, <span class="type">int</span>[] weights, <span class="type">int</span>[] values)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> weights.length;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 状态定义</span></span><br><span class="line">        <span class="type">int</span>[][] dp = <span class="keyword">new</span> <span class="title class_">int</span>[n + <span class="number">1</span>][W + <span class="number">1</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 初始条件：dp[0][j] = 0（0件物品价值为0）</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 状态转移</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt;= W; j++) &#123;</span><br><span class="line">                <span class="comment">// 不选第i件物品</span></span><br><span class="line">                dp[i][j] = dp[i-<span class="number">1</span>][j];</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 选第i件物品（前提是容量足够）</span></span><br><span class="line">                <span class="keyword">if</span> (j &gt;= weights[i-<span class="number">1</span>]) &#123;</span><br><span class="line">                    dp[i][j] = Math.max(dp[i][j], dp[i-<span class="number">1</span>][j-weights[i-<span class="number">1</span>]] + values[i-<span class="number">1</span>]);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> dp[n][W];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🎯 0/1背包问题 - 空间优化版本（滚动数组）</span></span><br><span class="line"><span class="comment">     * 💡 将二维DP优化为一维DP，空间复杂度从O(nW)降到O(W)</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">knapsackOptimized</span><span class="params">(<span class="type">int</span> W, <span class="type">int</span>[] weights, <span class="type">int</span>[] values)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> weights.length;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 一维状态定义</span></span><br><span class="line">        <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[W + <span class="number">1</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 状态转移（逆序遍历容量，避免重复选择）</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> W; j &gt;= weights[i]; j--) &#123;</span><br><span class="line">                <span class="comment">// 选或不选当前物品，取最大值</span></span><br><span class="line">                dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> dp[W];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🎯 背包问题变种 - 恰好装满背包</span></span><br><span class="line"><span class="comment">     * 💡 求恰好装满容量W的最大价值，若无法装满返回-1</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">knapsackExact</span><span class="params">(<span class="type">int</span> W, <span class="type">int</span>[] weights, <span class="type">int</span>[] values)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> weights.length;</span><br><span class="line">        <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[W + <span class="number">1</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 初始条件：除了dp[0]，其他都设为负无穷（表示不可达）</span></span><br><span class="line">        Arrays.fill(dp, Integer.MIN_VALUE);</span><br><span class="line">        dp[<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 状态转移</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> W; j &gt;= weights[i]; j--) &#123;</span><br><span class="line">                <span class="keyword">if</span> (dp[j - weights[i]] != Integer.MIN_VALUE) &#123;</span><br><span class="line">                    dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 如果dp[W]仍然是负无穷，说明无法恰好装满</span></span><br><span class="line">        <span class="keyword">return</span> dp[W] == Integer.MIN_VALUE ? -<span class="number">1</span> : dp[W];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="⚡-优化技巧与高级策略">⚡ 优化技巧与高级策略</h2><h3 id="🎯-空间优化技巧">🎯 空间优化技巧</h3><h4 id="1-滚动数组优化">1. 滚动数组优化</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 滚动数组优化 - 适用于只依赖前几个状态的情况</span></span><br><span class="line"><span class="comment"> * 💡 将O(n)空间优化为O(1)或O(k)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">rollingArrayOptimization</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) <span class="keyword">return</span> n;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 只需要前两个状态</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">prev2</span> <span class="operator">=</span> <span class="number">0</span>;  <span class="comment">// dp[i-2]</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">prev1</span> <span class="operator">=</span> <span class="number">1</span>;  <span class="comment">// dp[i-1]</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">current</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        current = prev1 + prev2;</span><br><span class="line">        prev2 = prev1;</span><br><span class="line">        prev1 = current;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> current;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-状态压缩">2. 状态压缩</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 状态压缩 - 用位运算表示状态</span></span><br><span class="line"><span class="comment"> * 💡 适用于状态是布尔值的情况</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">stateCompression</span><span class="params">(String s, String p)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">m</span> <span class="operator">=</span> s.length();</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> p.length();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 用位掩码表示状态</span></span><br><span class="line">    <span class="type">long</span>[] dp = <span class="keyword">new</span> <span class="title class_">long</span>[m + <span class="number">1</span>];</span><br><span class="line">    dp[<span class="number">0</span>] = <span class="number">1L</span>;  <span class="comment">// 初始状态</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt;= n; j++) &#123;</span><br><span class="line">        <span class="type">long</span>[] newDp = <span class="keyword">new</span> <span class="title class_">long</span>[m + <span class="number">1</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt;= m; i++) &#123;</span><br><span class="line">            <span class="comment">// 用位运算处理状态转移</span></span><br><span class="line">            <span class="keyword">if</span> (i &gt; <span class="number">0</span> &amp;&amp; (s.charAt(i-<span class="number">1</span>) == p.charAt(j-<span class="number">1</span>) || p.charAt(j-<span class="number">1</span>) == <span class="string">&#x27;.&#x27;</span>)) &#123;</span><br><span class="line">                newDp[i] |= dp[i-<span class="number">1</span>];</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 其他状态转移...</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        dp = newDp;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> dp[m] != <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="🎯-时间优化策略">🎯 时间优化策略</h3><h4 id="1-单调队列优化">1. 单调队列优化</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 单调队列优化 - 适用于滑动窗口最值问题</span></span><br><span class="line"><span class="comment"> * 💡 将O(n²)优化为O(n)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">maxSumWithMonotonicQueue</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> k)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> nums.length;</span><br><span class="line">    <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[n + <span class="number">1</span>];</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 单调队列，维护窗口中的最大值索引</span></span><br><span class="line">    Deque&lt;Integer&gt; deque = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="comment">// 维护队列单调性</span></span><br><span class="line">        <span class="keyword">while</span> (!deque.isEmpty() &amp;&amp; dp[deque.peekLast()] &lt;= dp[i-<span class="number">1</span>]) &#123;</span><br><span class="line">            deque.pollLast();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        deque.offerLast(i-<span class="number">1</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 移除超出窗口范围的元素</span></span><br><span class="line">        <span class="keyword">while</span> (deque.peekFirst() &lt; i - k) &#123;</span><br><span class="line">            deque.pollFirst();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 当前最大值</span></span><br><span class="line">        dp[i] = dp[deque.peekFirst()] + nums[i-<span class="number">1</span>];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> dp[n];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-斜率优化">2. 斜率优化</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 斜率优化 - 适用于特定形式的DP方程</span></span><br><span class="line"><span class="comment"> * 💡 将O(n²)优化为O(n)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">slopeOptimization</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> nums.length;</span><br><span class="line">    <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 凸包优化，维护决策点的凸包</span></span><br><span class="line">    List&lt;Integer&gt; convexHull = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">        <span class="comment">// 在凸包上二分查找最优决策点</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = convexHull.size() - <span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (left &lt; right) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">            <span class="keyword">if</span> (compareSlopes(convexHull.get(mid), convexHull.get(mid + <span class="number">1</span>), i) &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">                left = mid + <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                right = mid;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> <span class="variable">bestJ</span> <span class="operator">=</span> convexHull.get(left);</span><br><span class="line">        dp[i] = calculateValue(bestJ, i);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 维护凸包</span></span><br><span class="line">        <span class="keyword">while</span> (convexHull.size() &gt;= <span class="number">2</span> &amp;&amp; </span><br><span class="line">               compareSlopes(convexHull.get(convexHull.size()-<span class="number">2</span>), </span><br><span class="line">                           convexHull.get(convexHull.size()-<span class="number">1</span>), i) &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">            convexHull.remove(convexHull.size() - <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        convexHull.add(i);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> dp[n-<span class="number">1</span>];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="title function_">compareSlopes</span><span class="params">(<span class="type">int</span> j1, <span class="type">int</span> j2, <span class="type">int</span> i)</span> &#123;</span><br><span class="line">    <span class="comment">// 斜率比较逻辑...</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="title function_">calculateValue</span><span class="params">(<span class="type">int</span> j, <span class="type">int</span> i)</span> &#123;</span><br><span class="line">    <span class="comment">// 值计算逻辑...</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="🏢-实际应用案例">🏢 实际应用案例</h2><h3 id="案例1：股票买卖问题">案例1：股票买卖问题</h3><p><strong>场景描述</strong>：给定股票价格数组，求最大利润，有交易次数限制</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StockTrading</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🎯 股票买卖III - 最多两次交易</span></span><br><span class="line"><span class="comment">     * 📝 状态：buy1, sell1, buy2, sell2 表示第一次和第二次交易的状态</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">maxProfit</span><span class="params">(<span class="type">int</span>[] prices)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (prices == <span class="literal">null</span> || prices.length &lt; <span class="number">2</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 状态定义</span></span><br><span class="line">        <span class="comment">// firstBuy: 第一次买入后的最大利润（负数）</span></span><br><span class="line">        <span class="comment">// firstSell: 第一次卖出后的最大利润</span></span><br><span class="line">        <span class="comment">// secondBuy: 第二次买入后的最大利润</span></span><br><span class="line">        <span class="comment">// secondSell: 第二次卖出后的最大利润</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">firstBuy</span> <span class="operator">=</span> -prices[<span class="number">0</span>];</span><br><span class="line">        <span class="type">int</span> <span class="variable">firstSell</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">secondBuy</span> <span class="operator">=</span> -prices[<span class="number">0</span>];</span><br><span class="line">        <span class="type">int</span> <span class="variable">secondSell</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 状态转移</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; prices.length; i++) &#123;</span><br><span class="line">            firstBuy = Math.max(firstBuy, -prices[i]);  <span class="comment">// 第一次买入</span></span><br><span class="line">            firstSell = Math.max(firstSell, firstBuy + prices[i]);  <span class="comment">// 第一次卖出</span></span><br><span class="line">            secondBuy = Math.max(secondBuy, firstSell - prices[i]);  <span class="comment">// 第二次买入</span></span><br><span class="line">            secondSell = Math.max(secondSell, secondBuy + prices[i]);  <span class="comment">// 第二次卖出</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> secondSell;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🎯 股票买卖IV - 最多k次交易</span></span><br><span class="line"><span class="comment">     * 💡 通用解法，适用于任意交易次数限制</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">maxProfit</span><span class="params">(<span class="type">int</span> k, <span class="type">int</span>[] prices)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> prices.length;</span><br><span class="line">        <span class="keyword">if</span> (n &lt; <span class="number">2</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 如果k很大，相当于没有限制</span></span><br><span class="line">        <span class="keyword">if</span> (k &gt;= n / <span class="number">2</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> maxProfitUnlimited(prices);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 状态定义</span></span><br><span class="line">        <span class="comment">// dp[i][j] 表示最多i次交易，第j天的最大利润</span></span><br><span class="line">        <span class="type">int</span>[][] dp = <span class="keyword">new</span> <span class="title class_">int</span>[k + <span class="number">1</span>][n];</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 状态转移</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= k; i++) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">maxDiff</span> <span class="operator">=</span> -prices[<span class="number">0</span>];  <span class="comment">// 记录之前的最大利润差</span></span><br><span class="line">            </span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt; n; j++) &#123;</span><br><span class="line">                <span class="comment">// 要么今天不交易，要么今天卖出</span></span><br><span class="line">                dp[i][j] = Math.max(dp[i][j-<span class="number">1</span>], prices[j] + maxDiff);</span><br><span class="line">                <span class="comment">// 更新最大利润差，用于明天的交易</span></span><br><span class="line">                maxDiff = Math.max(maxDiff, dp[i-<span class="number">1</span>][j] - prices[j]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> dp[k][n-<span class="number">1</span>];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="title function_">maxProfitUnlimited</span><span class="params">(<span class="type">int</span>[] prices)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">profit</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; prices.length; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (prices[i] &gt; prices[i-<span class="number">1</span>]) &#123;</span><br><span class="line">                profit += prices[i] - prices[i-<span class="number">1</span>];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> profit;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="案例2：任务调度问题">案例2：任务调度问题</h3><p><strong>场景描述</strong>：有n个任务，每个任务有截止时间和利润，安排任务执行顺序使总利润最大</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TaskSchedulingDP</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Task</span> &#123;</span><br><span class="line">        <span class="type">int</span> deadline;</span><br><span class="line">        <span class="type">int</span> profit;</span><br><span class="line">        </span><br><span class="line">        Task(<span class="type">int</span> deadline, <span class="type">int</span> profit) &#123;</span><br><span class="line">            <span class="built_in">this</span>.deadline = deadline;</span><br><span class="line">            <span class="built_in">this</span>.profit = profit;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🎯 任务调度 - 最大利润</span></span><br><span class="line"><span class="comment">     * 📝 状态：dp[i] 表示在时间i内能获得的最大利润</span></span><br><span class="line"><span class="comment">     * 💡 按截止时间排序，使用动态规划</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">scheduleTasks</span><span class="params">(Task[] tasks)</span> &#123;</span><br><span class="line">        <span class="comment">// 按截止时间排序</span></span><br><span class="line">        Arrays.sort(tasks, (a, b) -&gt; a.deadline - b.deadline);</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> <span class="variable">maxDeadline</span> <span class="operator">=</span> tasks[tasks.length - <span class="number">1</span>].deadline;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 状态定义</span></span><br><span class="line">        <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[maxDeadline + <span class="number">1</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> <span class="variable">taskIndex</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 状态转移</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">time</span> <span class="operator">=</span> <span class="number">1</span>; time &lt;= maxDeadline; time++) &#123;</span><br><span class="line">            dp[time] = dp[time - <span class="number">1</span>];  <span class="comment">// 不执行任何任务</span></span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 考虑在时间time执行的任务</span></span><br><span class="line">            <span class="keyword">while</span> (taskIndex &lt; tasks.length &amp;&amp; tasks[taskIndex].deadline == time) &#123;</span><br><span class="line">                <span class="type">Task</span> <span class="variable">task</span> <span class="operator">=</span> tasks[taskIndex];</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 要么不执行这个任务，要么执行这个任务</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">start</span> <span class="operator">=</span> <span class="number">0</span>; start &lt; time; start++) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (dp[start] + task.profit &gt; dp[time]) &#123;</span><br><span class="line">                        dp[time] = dp[start] + task.profit;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                </span><br><span class="line">                taskIndex++;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> dp[maxDeadline];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🎯 任务调度 - 带权重的完成时间最小化</span></span><br><span class="line"><span class="comment">     * 📝 状态：dp[i][j] 表示前i个任务，总完成时间为j的最小加权完成时间</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">minimizeWeightedCompletionTime</span><span class="params">(<span class="type">int</span>[] durations, <span class="type">int</span>[] weights)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> durations.length;</span><br><span class="line">        <span class="type">int</span> <span class="variable">totalTime</span> <span class="operator">=</span> Arrays.stream(durations).sum();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 状态定义</span></span><br><span class="line">        <span class="type">int</span>[][] dp = <span class="keyword">new</span> <span class="title class_">int</span>[n + <span class="number">1</span>][totalTime + <span class="number">1</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 状态转移</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt;= totalTime; j++) &#123;</span><br><span class="line">                dp[i][j] = dp[i-<span class="number">1</span>][j];  <span class="comment">// 不选择第i个任务</span></span><br><span class="line">                </span><br><span class="line">                <span class="keyword">if</span> (j &gt;= durations[i-<span class="number">1</span>]) &#123;</span><br><span class="line">                    <span class="type">int</span> <span class="variable">completionTime</span> <span class="operator">=</span> j;</span><br><span class="line">                    <span class="type">int</span> <span class="variable">weightedTime</span> <span class="operator">=</span> weights[i-<span class="number">1</span>] * completionTime;</span><br><span class="line">                    dp[i][j] = Math.min(dp[i][j], dp[i-<span class="number">1</span>][j - durations[i-<span class="number">1</span>]] + weightedTime);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> dp[n][totalTime];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="⚡-性能分析">⚡ 性能分析</h2><h3 id="时间复杂度">时间复杂度</h3><p>动态规划的时间复杂度取决于状态数和状态转移复杂度：</p><table><thead><tr><th>问题类型</th><th>状态数</th><th>转移复杂度</th><th>总复杂度</th><th>示例</th></tr></thead><tbody><tr><td>一维线性DP</td><td>O(n)</td><td>O(1)</td><td><strong>O(n)</strong></td><td>斐波那契数列</td></tr><tr><td>一维线性DP</td><td>O(n)</td><td>O(n)</td><td><strong>O(n²)</strong></td><td>最长递增子序列</td></tr><tr><td>二维DP</td><td>O(n²)</td><td>O(1)</td><td><strong>O(n²)</strong></td><td>编辑距离</td></tr><tr><td>二维DP</td><td>O(n²)</td><td>O(n)</td><td><strong>O(n³)</strong></td><td>最优三角形剖分</td></tr><tr><td>三维DP</td><td>O(n³)</td><td>O(1)</td><td><strong>O(n³)</strong></td><td>矩阵链乘法</td></tr><tr><td>状态压缩DP</td><td>O(2ⁿ)</td><td>O(1)</td><td><strong>O(2ⁿ)</strong></td><td>旅行商问题</td></tr></tbody></table><h3 id="空间复杂度">空间复杂度</h3><ul><li><strong>基础DP</strong>：O(n) 或 O(n²)，存储所有状态</li><li><strong>滚动数组优化</strong>：O(1) 或 O(n)，只保留必要状态</li><li><strong>状态压缩</strong>：O(2ⁿ) 或 O(n×2ⁿ)，用位运算表示状态</li></ul><h3 id="优化效果对比">优化效果对比</h3><table><thead><tr><th>优化方法</th><th>时间复杂度</th><th>空间复杂度</th><th>适用场景</th><th>优化效果</th></tr></thead><tbody><tr><td><strong>基础DP</strong></td><td>O(n²)</td><td>O(n²)</td><td>通用</td><td>基准线</td></tr><tr><td><strong>滚动数组</strong></td><td>O(n²)</td><td><strong>O(n)</strong></td><td>状态只依赖前几行</td><td>空间优化</td></tr><tr><td><strong>单调队列</strong></td><td><strong>O(n)</strong></td><td>O(n)</td><td>滑动窗口最值</td><td>时间优化</td></tr><tr><td><strong>斜率优化</strong></td><td><strong>O(n)</strong></td><td>O(n)</td><td>特定形式DP方程</td><td>时间优化</td></tr><tr><td><strong>状态压缩</strong></td><td>O(2ⁿ)</td><td><strong>O(2ⁿ)</strong></td><td>状态是布尔值</td><td>空间优化</td></tr></tbody></table><hr><h2 id="🎯-实战练习：LeetCode经典DP题目">🎯 实战练习：LeetCode经典DP题目</h2><blockquote><p>🎯 <strong>练习目标</strong>：通过刷题巩固动态规划核心技巧</p><p>📊 <strong>难度分布</strong>：简单(30%) | 中等(50%) | 困难(20%)</p><p>💡 <strong>练习建议</strong>：按类型刷题，总结规律，形成直觉</p></blockquote><h3 id="🌟-基础题目（必做）">🌟 基础题目（必做）</h3><h4 id="1-线性DP（一维）">1. 线性DP（一维）</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 LeetCode 70. 爬楼梯</span></span><br><span class="line"><span class="comment"> * 💡 最基础的DP入门题</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">climbStairs</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">2</span>) <span class="keyword">return</span> n;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">prev2</span> <span class="operator">=</span> <span class="number">1</span>, prev1 = <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">3</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">current</span> <span class="operator">=</span> prev1 + prev2;</span><br><span class="line">        prev2 = prev1;</span><br><span class="line">        prev1 = current;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> prev1;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 LeetCode 198. 打家劫舍</span></span><br><span class="line"><span class="comment"> * 💡 不能连续选择相邻元素的DP问题</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">rob</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (nums == <span class="literal">null</span> || nums.length == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> (nums.length == <span class="number">1</span>) <span class="keyword">return</span> nums[<span class="number">0</span>];</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">prev2</span> <span class="operator">=</span> <span class="number">0</span>;  <span class="comment">// dp[i-2]</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">prev1</span> <span class="operator">=</span> <span class="number">0</span>;  <span class="comment">// dp[i-1]</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> num : nums) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">current</span> <span class="operator">=</span> Math.max(prev1, prev2 + num);</span><br><span class="line">        prev2 = prev1;</span><br><span class="line">        prev1 = current;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> prev1;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 LeetCode 413. 等差数列划分</span></span><br><span class="line"><span class="comment"> * 💡 需要找规律的DP问题</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">numberOfArithmeticSlices</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> nums.length;</span><br><span class="line">    <span class="keyword">if</span> (n &lt; <span class="number">3</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">    <span class="type">int</span> <span class="variable">total</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">2</span>; i &lt; n; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (nums[i] - nums[i-<span class="number">1</span>] == nums[i-<span class="number">1</span>] - nums[i-<span class="number">2</span>]) &#123;</span><br><span class="line">            dp[i] = dp[i-<span class="number">1</span>] + <span class="number">1</span>;</span><br><span class="line">            total += dp[i];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> total;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-背包问题">2. 背包问题</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 LeetCode 416. 分割等和子集</span></span><br><span class="line"><span class="comment"> * 💡 0-1背包问题的变种</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">canPartition</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> Arrays.stream(nums).sum();</span><br><span class="line">    <span class="keyword">if</span> (sum % <span class="number">2</span> != <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">target</span> <span class="operator">=</span> sum / <span class="number">2</span>;</span><br><span class="line">    <span class="type">boolean</span>[] dp = <span class="keyword">new</span> <span class="title class_">boolean</span>[target + <span class="number">1</span>];</span><br><span class="line">    dp[<span class="number">0</span>] = <span class="literal">true</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> num : nums) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> target; j &gt;= num; j--) &#123;</span><br><span class="line">            dp[j] = dp[j] || dp[j - num];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> dp[target];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 LeetCode 494. 目标和</span></span><br><span class="line"><span class="comment"> * 💡 背包问题求方案数</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findTargetSumWays</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> Arrays.stream(nums).sum();</span><br><span class="line">    <span class="keyword">if</span> (Math.abs(target) &gt; sum) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">newTarget</span> <span class="operator">=</span> (sum + target) / <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">if</span> ((sum + target) % <span class="number">2</span> != <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[newTarget + <span class="number">1</span>];</span><br><span class="line">    dp[<span class="number">0</span>] = <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> num : nums) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> newTarget; j &gt;= num; j--) &#123;</span><br><span class="line">            dp[j] += dp[j - num];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> dp[newTarget];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 LeetCode 474. 一和零</span></span><br><span class="line"><span class="comment"> * 💡 多维背包问题</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findMaxForm</span><span class="params">(String[] strs, <span class="type">int</span> m, <span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="type">int</span>[][] dp = <span class="keyword">new</span> <span class="title class_">int</span>[m + <span class="number">1</span>][n + <span class="number">1</span>];</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (String str : strs) &#123;</span><br><span class="line">        <span class="type">int</span>[] count = countZerosAndOnes(str);</span><br><span class="line">        <span class="type">int</span> <span class="variable">zeros</span> <span class="operator">=</span> count[<span class="number">0</span>];</span><br><span class="line">        <span class="type">int</span> <span class="variable">ones</span> <span class="operator">=</span> count[<span class="number">1</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> m; i &gt;= zeros; i--) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> n; j &gt;= ones; j--) &#123;</span><br><span class="line">                dp[i][j] = Math.max(dp[i][j], dp[i - zeros][j - ones] + <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> dp[m][n];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span>[] countZerosAndOnes(String str) &#123;</span><br><span class="line">    <span class="type">int</span>[] count = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">2</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">char</span> c : str.toCharArray()) &#123;</span><br><span class="line">        count[c - <span class="string">&#x27;0&#x27;</span>]++;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> count;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="🚀-进阶题目（选做）">🚀 进阶题目（选做）</h3><h4 id="1-区间DP">1. 区间DP</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 LeetCode 516. 最长回文子序列</span></span><br><span class="line"><span class="comment"> * 💡 区间DP的经典题目</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">longestPalindromeSubseq</span><span class="params">(String s)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> s.length();</span><br><span class="line">    <span class="type">int</span>[][] dp = <span class="keyword">new</span> <span class="title class_">int</span>[n][n];</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 单个字符是长度为1的回文</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">        dp[i][i] = <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 区间长度从2到n</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> <span class="number">2</span>; len &lt;= n; len++) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt;= n - len; i++) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> i + len - <span class="number">1</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (s.charAt(i) == s.charAt(j)) &#123;</span><br><span class="line">                dp[i][j] = dp[i+<span class="number">1</span>][j-<span class="number">1</span>] + <span class="number">2</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                dp[i][j] = Math.max(dp[i+<span class="number">1</span>][j], dp[i][j-<span class="number">1</span>]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> dp[<span class="number">0</span>][n-<span class="number">1</span>];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 LeetCode 647. 回文子串</span></span><br><span class="line"><span class="comment"> * 💡 计数型的区间DP</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">countSubstrings</span><span class="params">(String s)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> s.length();</span><br><span class="line">    <span class="type">boolean</span>[][] dp = <span class="keyword">new</span> <span class="title class_">boolean</span>[n][n];</span><br><span class="line">    <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> n - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> i; j &lt; n; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (s.charAt(i) == s.charAt(j)) &#123;</span><br><span class="line">                <span class="keyword">if</span> (j - i &lt;= <span class="number">1</span>) &#123;</span><br><span class="line">                    dp[i][j] = <span class="literal">true</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    dp[i][j] = dp[i+<span class="number">1</span>][j-<span class="number">1</span>];</span><br><span class="line">                &#125;</span><br><span class="line">                </span><br><span class="line">                <span class="keyword">if</span> (dp[i][j]) &#123;</span><br><span class="line">                    count++;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> count;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-状态压缩DP">2. 状态压缩DP</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 LeetCode 691. 贴纸拼词</span></span><br><span class="line"><span class="comment"> * 💡 状态压缩DP，用位掩码表示字符使用情况</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">minStickers</span><span class="params">(String[] stickers, String target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">m</span> <span class="operator">=</span> target.length();</span><br><span class="line">    <span class="type">int</span> <span class="variable">finalState</span> <span class="operator">=</span> (<span class="number">1</span> &lt;&lt; m) - <span class="number">1</span>;  <span class="comment">// 所有字符都被使用的状态</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 预处理每个贴纸能提供哪些字符</span></span><br><span class="line">    <span class="type">int</span>[][] stickerCounts = <span class="keyword">new</span> <span class="title class_">int</span>[stickers.length][<span class="number">26</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; stickers.length; i++) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">char</span> c : stickers[i].toCharArray()) &#123;</span><br><span class="line">            stickerCounts[i][c - <span class="string">&#x27;a&#x27;</span>]++;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// dp[state] 表示达到state状态需要的最少贴纸数</span></span><br><span class="line">    <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[finalState + <span class="number">1</span>];</span><br><span class="line">    Arrays.fill(dp, Integer.MAX_VALUE);</span><br><span class="line">    dp[<span class="number">0</span>] = <span class="number">0</span>;  <span class="comment">// 初始状态不需要贴纸</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">state</span> <span class="operator">=</span> <span class="number">0</span>; state &lt; finalState; state++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (dp[state] == Integer.MAX_VALUE) <span class="keyword">continue</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 尝试使用每个贴纸</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; stickers.length; i++) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">newState</span> <span class="operator">=</span> state;</span><br><span class="line">            <span class="type">int</span>[] tempCounts = stickerCounts[i].clone();</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 使用贴纸i能覆盖target中的哪些字符</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; m; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (((state &gt;&gt; j) &amp; <span class="number">1</span>) == <span class="number">0</span> &amp;&amp; tempCounts[target.charAt(j) - <span class="string">&#x27;a&#x27;</span>] &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                    newState |= (<span class="number">1</span> &lt;&lt; j);</span><br><span class="line">                    tempCounts[target.charAt(j) - <span class="string">&#x27;a&#x27;</span>]--;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (newState != state) &#123;</span><br><span class="line">                dp[newState] = Math.min(dp[newState], dp[state] + <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> dp[finalState] == Integer.MAX_VALUE ? -<span class="number">1</span> : dp[finalState];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="📊-相关LeetCode题目">📊 相关LeetCode题目</h2><h3 id="🎯-基础题目（⭐-简单）">🎯 基础题目（⭐ 简单）</h3><ol><li><strong>509. 斐波那契数</strong> - 最基础的DP入门</li><li><strong>70. 爬楼梯</strong> - 线性DP的经典入门</li><li><strong>746. 使用最小花费爬楼梯</strong> - 带权重的爬楼梯</li><li><strong>198. 打家劫舍</strong> - 不能相邻选择的DP</li><li><strong>413. 等差数列划分</strong> - 需要发现规律的DP</li></ol><h3 id="🚀-进阶题目（⭐⭐-中等）">🚀 进阶题目（⭐⭐ 中等）</h3><ol><li><strong>322. 零钱兑换</strong> - 完全背包问题的经典应用</li><li><strong>300. 最长递增子序列</strong> - 序列DP的经典题目</li><li><strong>1143. 最长公共子序列</strong> - 两个序列的DP匹配</li><li><strong>416. 分割等和子集</strong> - 0-1背包问题的变种</li><li><strong>494. 目标和</strong> - 背包问题求方案数</li></ol><h3 id="🔥-高级题目（⭐⭐⭐-困难）">🔥 高级题目（⭐⭐⭐ 困难）</h3><ol><li><strong>72. 编辑距离</strong> - 二维DP的经典应用</li><li><strong>10. 正则表达式匹配</strong> - 带通配符的复杂DP</li><li><strong>115. 不同的子序列</strong> - 复杂的字符串DP匹配</li><li><strong>123. 买卖股票的最佳时机III</strong> - 多状态DP管理</li><li><strong>188. 买卖股票的最佳时机IV</strong> - 通用股票交易DP</li></ol><hr><h2 id="🧠-记忆口诀">🧠 记忆口诀</h2><h3 id="🎯-动态规划四要素">🎯 动态规划四要素</h3><ol><li><strong>状态定义</strong>：找到能描述子问题的变量 📊</li><li><strong>状态转移</strong>：发现状态之间的关系 🔄</li><li><strong>初始条件</strong>：设置最小子问题的解 🎯</li><li><strong>最终结果</strong>：从dp数组中提取答案 ✅</li></ol><h3 id="🔍-DP问题识别口诀">🔍 DP问题识别口诀</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">📝 最优子结构？能分解为子问题！</span><br><span class="line">🔄 重叠子问题？子问题重复出现！</span><br><span class="line">⚡ 无后效性？未来不影响过去！</span><br><span class="line">💾 存储子解？避免重复计算！</span><br></pre></td></tr></table></figure><h3 id="🎨-状态定义技巧">🎨 状态定义技巧</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">📏 一维线性：dp[i] 表示前i个元素</span><br><span class="line">🔄 二维表格：dp[i][j] 表示两个序列匹配</span><br><span class="line">⏰ 时间维度：dp[i][t] 表示前i个元素时间t的状态</span><br><span class="line">💰 多重约束：dp[i][j][k] 表示多个维度的状态</span><br></pre></td></tr></table></figure><h3 id="⚡-优化口诀">⚡ 优化口诀</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">🚀 滚动数组：空间换时间，只保留必要状态！</span><br><span class="line">🔪 单调队列：滑动窗口最值，O(n²)变O(n)！</span><br><span class="line">📐 斜率优化：特定方程形式，大幅提升效率！</span><br><span class="line">🔧 状态压缩：位运算表示状态，节省空间！</span><br></pre></td></tr></table></figure><hr><h2 id="📖-参考资料">📖 参考资料</h2><ul><li><a href="https://book.douban.com/subject/1885170/">算法导论（第3版）</a> - 第15章 动态规划</li><li><a href="https://book.douban.com/subject/19952400/">算法（第4版）</a> - 第6章 动态规划</li><li><a href="https://leetcode.cn/tag/dynamic-programming/">LeetCode动态规划标签</a> 🇨🇳</li><li><a href="https://zh.wikipedia.org/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92">动态规划维基百科</a> 🇨🇳</li><li><a href="https://github.com/tianyicui/pack">背包问题九讲</a> 🇨🇳</li><li><a href="https://zhuanlan.zhihu.com/p/31628866">动态规划专题 - 知乎专栏</a> 🇨🇳</li><li><a href="https://leetcode.cn/tag/dynamic-programming/problems/">力扣动态规划题库</a> 🇨🇳</li></ul><hr><h2 id="🎓-学习总结：动态规划知识图谱">🎓 学习总结：动态规划知识图谱</h2><h3 id="🗺️-知识架构图">🗺️ 知识架构图</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">动态规划 🦋</span><br><span class="line">├── 核心思想 (最优子结构 + 重叠子问题)</span><br><span class="line">├── 基本框架 (状态定义 + 转移方程 + 初始条件)</span><br><span class="line">├── 解题四步法 (分析 → 定义 → 转移 → 优化)</span><br><span class="line">├── 经典问题 (斐波那契 + 背包 + LIS + 编辑距离)</span><br><span class="line">├── 优化技巧 (滚动数组 + 单调队列 + 斜率优化)</span><br><span class="line">├── 高级应用 (区间DP + 状态压缩 + 树形DP)</span><br><span class="line">└── 实战练习 (LeetCode 经典DP题库)</span><br></pre></td></tr></table></figure><h3 id="🎯-掌握程度自测">🎯 掌握程度自测</h3><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>能独立解决基础DP问题</td><td>解决爬楼梯和零钱兑换</td></tr><tr><td><strong>精通</strong> ⭐⭐⭐</td><td>掌握优化技巧，能举一反三</td><td>解决LIS和编辑距离优化</td></tr><tr><td><strong>专家</strong> ⭐⭐⭐⭐</td><td>能解决复杂DP问题</td><td>解决股票交易和正则匹配</td></tr></tbody></table><h3 id="🚀-进阶学习路线">🚀 进阶学习路线</h3><ol><li><strong>🎯 基础巩固</strong>：熟练掌握三大DP类型（线性、背包、序列）</li><li><strong>⚡ 技巧提升</strong>：深入学习空间优化和时间优化策略</li><li><strong>🏗️ 高级应用</strong>：掌握区间DP、状态压缩DP、树形DP</li><li><strong>🔬 算法研究</strong>：探索四边形不等式、凸优化等高级技巧</li></ol><h3 id="💡-学习建议">💡 学习建议</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">📝 建议1：从简单问题入手，理解DP核心思想</span><br><span class="line">📝 建议2：多画图辅助理解，可视化状态转移过程</span><br><span class="line">📝 建议3：重视状态定义，这是DP解题的关键</span><br><span class="line">📝 建议4：掌握优化技巧，提升算法效率</span><br><span class="line">📝 建议5：按类型刷题，总结规律和模板</span><br></pre></td></tr></table></figure><hr><p><em>最后更新：2025-01-15</em></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;🦋 动态规划详解：从入门到精通&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;✨ &lt;strong&gt;一文掌握动态规划精髓&lt;/strong&gt;：状态定义 + 状态转移 + 最优子结构 = 高效解题&lt;/p&gt;
&lt;p&gt;🎯 &lt;strong&gt;核心思想&lt;/strong&gt;：将复杂问题分解为重</summary>
      
    
    
    
    <category term="书山指路" scheme="https://blog.adoreorg.cn/categories/%E4%B9%A6%E5%B1%B1%E6%8C%87%E8%B7%AF/"/>
    
    
    <category term="算法" scheme="https://blog.adoreorg.cn/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="数据结构" scheme="https://blog.adoreorg.cn/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    
    <category term="LeetCode" scheme="https://blog.adoreorg.cn/tags/LeetCode/"/>
    
    <category term="ACM" scheme="https://blog.adoreorg.cn/tags/ACM/"/>
    
    <category term="动态规划" scheme="https://blog.adoreorg.cn/tags/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/"/>
    
    <category term="DP" scheme="https://blog.adoreorg.cn/tags/DP/"/>
    
    <category term="最优化" scheme="https://blog.adoreorg.cn/tags/%E6%9C%80%E4%BC%98%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>二分查找算法及变种形式详解</title>
    <link href="https://blog.adoreorg.cn/posts/eb5gs334.html"/>
    <id>https://blog.adoreorg.cn/posts/eb5gs334.html</id>
    <published>2025-01-14T16:00:00.000Z</published>
    <updated>2025-10-07T15:41:58.256Z</updated>
    
    <content type="html"><![CDATA[<h1>🔍 二分查找算法及变种形式详解</h1><h2 id="📖-概述">📖 概述</h2><p>二分查找（Binary Search）是计算机科学中最基础、最重要的算法之一。它通过每次将搜索范围缩小一半的方式，在有序数组中高效地查找目标元素，时间复杂度为 <strong>O(log n)</strong>。</p><h2 id="🎯-核心思想">🎯 核心思想</h2><p>二分查找的核心思想是：<strong>分治</strong>。通过比较中间元素与目标值，每次都能将搜索范围缩小一半，直到找到目标值或确定不存在。</p><h2 id="🔢-基础二分查找">🔢 基础二分查找</h2><h3 id="标准模板">标准模板</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BinarySearch</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 基础二分查找 - 查找目标值的索引</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> nums 有序数组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> target 目标值</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 目标值的索引，不存在返回 -1</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>; <span class="comment">// 防止溢出</span></span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (nums[mid] == target) &#123;</span><br><span class="line">                <span class="keyword">return</span> mid; <span class="comment">// 找到目标</span></span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (nums[mid] &lt; target) &#123;</span><br><span class="line">                left = mid + <span class="number">1</span>; <span class="comment">// 在右半部分继续查找</span></span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                right = mid - <span class="number">1</span>; <span class="comment">// 在左半部分继续查找</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> -<span class="number">1</span>; <span class="comment">// 未找到目标</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="基础二分查找测试">基础二分查找测试</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testBasicBinarySearch</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">BinarySearch</span> <span class="variable">bs</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BinarySearch</span>();</span><br><span class="line">    <span class="type">int</span>[] nums = &#123;<span class="number">1</span>, <span class="number">3</span>, <span class="number">5</span>, <span class="number">7</span>, <span class="number">9</span>, <span class="number">11</span>, <span class="number">13</span>, <span class="number">15</span>&#125;;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 测试存在的元素</span></span><br><span class="line">    assertEquals(<span class="number">0</span>, bs.binarySearch(nums, <span class="number">1</span>));  <span class="comment">// 最左元素</span></span><br><span class="line">    assertEquals(<span class="number">7</span>, bs.binarySearch(nums, <span class="number">15</span>)); <span class="comment">// 最右元素</span></span><br><span class="line">    assertEquals(<span class="number">3</span>, bs.binarySearch(nums, <span class="number">7</span>));  <span class="comment">// 中间元素</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 测试不存在的元素</span></span><br><span class="line">    assertEquals(-<span class="number">1</span>, bs.binarySearch(nums, <span class="number">0</span>));   <span class="comment">// 小于最小值</span></span><br><span class="line">    assertEquals(-<span class="number">1</span>, bs.binarySearch(nums, <span class="number">16</span>));  <span class="comment">// 大于最大值</span></span><br><span class="line">    assertEquals(-<span class="number">1</span>, bs.binarySearch(nums, <span class="number">6</span>));   <span class="comment">// 中间缺失值</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="🔄-二分查找的变种形式">🔄 二分查找的变种形式</h2><h3 id="1-查找第一个等于目标值的元素">1. 查找第一个等于目标值的元素</h3><p><strong>场景</strong>：数组中可能有重复元素，需要找到第一个等于目标值的位置</p><h4 id="原始实现（可优化）">原始实现（可优化）</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 查找第一个等于目标值的元素 - 原始版本</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findFirstEqual</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) &#123;</span><br><span class="line">            left = mid + <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (nums[mid] &gt; target) &#123;</span><br><span class="line">            right = mid - <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 找到目标值，但需要确认是否是第一个</span></span><br><span class="line">            <span class="keyword">if</span> (mid == <span class="number">0</span> || nums[mid - <span class="number">1</span>] != target) &#123;</span><br><span class="line">                <span class="keyword">return</span> mid;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                right = mid - <span class="number">1</span>; <span class="comment">// 继续在左半部分查找</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="优化实现一：简化逻辑版">优化实现一：简化逻辑版</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 查找第一个等于目标值的元素 - 优化版本1</span></span><br><span class="line"><span class="comment"> * 简化逻辑，减少嵌套层级</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findFirstEqualOptimized</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &gt;= target) &#123;</span><br><span class="line">            right = mid - <span class="number">1</span>;  <span class="comment">// 向左收缩，寻找第一个等于target的位置</span></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            left = mid + <span class="number">1</span>;   <span class="comment">// 向右收缩</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 检查left是否在有效范围内，且nums[left]是否等于target</span></span><br><span class="line">    <span class="keyword">return</span> (left &lt; nums.length &amp;&amp; nums[left] == target) ? left : -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-查找最后一个等于目标值的元素">2. 查找最后一个等于目标值的元素</h3><h4 id="原始实现（可优化）-2">原始实现（可优化）</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 查找最后一个等于目标值的元素 - 原始版本</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findLastEqual</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) &#123;</span><br><span class="line">            left = mid + <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (nums[mid] &gt; target) &#123;</span><br><span class="line">            right = mid - <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 找到目标值，但需要确认是否是最后一个</span></span><br><span class="line">            <span class="keyword">if</span> (mid == nums.length - <span class="number">1</span> || nums[mid + <span class="number">1</span>] != target) &#123;</span><br><span class="line">                <span class="keyword">return</span> mid;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                left = mid + <span class="number">1</span>; <span class="comment">// 继续在右半部分查找</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="优化实现一：简化逻辑版-2">优化实现一：简化逻辑版</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 查找最后一个等于目标值的元素 - 优化版本1</span></span><br><span class="line"><span class="comment"> * 简化逻辑，减少嵌套层级</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findLastEqualOptimized</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt;= target) &#123;</span><br><span class="line">            left = mid + <span class="number">1</span>;  <span class="comment">// 向右收缩，寻找最后一个等于target的位置</span></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            right = mid - <span class="number">1</span>; <span class="comment">// 向左收缩</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 检查right是否在有效范围内，且nums[right]是否等于target</span></span><br><span class="line">    <span class="keyword">return</span> (right &gt;= <span class="number">0</span> &amp;&amp; nums[right] == target) ? right : -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-查找第一个大于等于目标值的元素">3. 查找第一个大于等于目标值的元素</h3><h4 id="原始实现（可优化）-3">原始实现（可优化）</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 查找第一个大于等于目标值的元素 - 原始版本</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findFirstGreaterOrEqual</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) &#123;</span><br><span class="line">            left = mid + <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// nums[mid] &gt;= target</span></span><br><span class="line">            <span class="keyword">if</span> (mid == <span class="number">0</span> || nums[mid - <span class="number">1</span>] &lt; target) &#123;</span><br><span class="line">                <span class="keyword">return</span> mid;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                right = mid - <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="优化实现一：简化逻辑版-3">优化实现一：简化逻辑版</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 查找第一个大于等于目标值的元素 - 优化版本1</span></span><br><span class="line"><span class="comment"> * 简化逻辑，减少嵌套层级</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findFirstGreaterOrEqualOptimized</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &gt;= target) &#123;</span><br><span class="line">            right = mid - <span class="number">1</span>;  <span class="comment">// 向左收缩，寻找第一个大于等于target的位置</span></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            left = mid + <span class="number">1</span>;   <span class="comment">// 向右收缩</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 检查left是否在有效范围内，且nums[left]是否大于等于target</span></span><br><span class="line">    <span class="keyword">return</span> (left &lt; nums.length &amp;&amp; nums[left] &gt;= target) ? left : -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-查找最后一个小于等于目标值的元素">4. 查找最后一个小于等于目标值的元素</h3><h4 id="原始实现（可优化）-4">原始实现（可优化）</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 查找最后一个小于等于目标值的元素 - 原始版本</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findLastLessOrEqual</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &gt; target) &#123;</span><br><span class="line">            right = mid - <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// nums[mid] &lt;= target</span></span><br><span class="line">            <span class="keyword">if</span> (mid == nums.length - <span class="number">1</span> || nums[mid + <span class="number">1</span>] &gt; target) &#123;</span><br><span class="line">                <span class="keyword">return</span> mid;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                left = mid + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="优化实现一：简化逻辑版-4">优化实现一：简化逻辑版</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 查找最后一个小于等于目标值的元素 - 优化版本1</span></span><br><span class="line"><span class="comment"> * 简化逻辑，减少嵌套层级</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findLastLessOrEqualOptimized</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt;= target) &#123;</span><br><span class="line">            left = mid + <span class="number">1</span>;  <span class="comment">// 向右收缩，寻找最后一个小于等于target的位置</span></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            right = mid - <span class="number">1</span>; <span class="comment">// 向左收缩</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 检查right是否在有效范围内，且nums[right]是否小于等于target</span></span><br><span class="line">    <span class="keyword">return</span> (right &gt;= <span class="number">0</span> &amp;&amp; nums[right] &lt;= target) ? right : -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="🎯-应用案例详解">🎯 应用案例详解</h2><h3 id="案例1：在排序数组中查找元素的第一个和最后一个位置">案例1：在排序数组中查找元素的第一个和最后一个位置</h3><p><strong>LeetCode 34</strong>：给定一个按照升序排列的数组，找到目标值的开始和结束位置。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 查找第一个大于等于目标值的元素位置</span></span><br><span class="line"><span class="comment">     * 使用循环不变量思想，简化逻辑</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="title function_">lowerBound</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>; <span class="comment">// 闭区间 [left, right]</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (left &lt;= right) &#123; <span class="comment">// 区间不为空</span></span><br><span class="line">            <span class="comment">// 循环不变量：</span></span><br><span class="line">            <span class="comment">// nums[left-1] &lt; target</span></span><br><span class="line">            <span class="comment">// nums[right+1] &gt;= target</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (nums[mid] &gt;= target) &#123;</span><br><span class="line">                right = mid - <span class="number">1</span>; <span class="comment">// 范围缩小到 [left, mid-1]</span></span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                left = mid + <span class="number">1</span>; <span class="comment">// 范围缩小到 [mid+1, right]</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 循环结束后 left = right+1</span></span><br><span class="line">        <span class="comment">// 此时 nums[left-1] &lt; target 而 nums[left] = nums[right+1] &gt;= target</span></span><br><span class="line">        <span class="comment">// 所以 left 就是第一个 &gt;= target 的元素下标</span></span><br><span class="line">        <span class="keyword">return</span> left;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span>[] searchRange(<span class="type">int</span>[] nums, <span class="type">int</span> target) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">start</span> <span class="operator">=</span> lowerBound(nums, target);</span><br><span class="line">        <span class="comment">// 检查是否找到目标值</span></span><br><span class="line">        <span class="keyword">if</span> (start == nums.length || nums[start] != target) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[]&#123;-<span class="number">1</span>, -<span class="number">1</span>&#125;; <span class="comment">// nums 中没有 target</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 查找最后一个等于目标值的位置</span></span><br><span class="line">        <span class="comment">// 通过查找第一个大于target的元素位置，然后减1</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">end</span> <span class="operator">=</span> lowerBound(nums, target + <span class="number">1</span>) - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[]&#123;start, end&#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="案例2：搜索插入位置">案例2：搜索插入位置</h3><p><strong>LeetCode 35</strong>：给定一个排序数组和一个目标值，如果目标值存在于数组中，返回其索引；如果不存在，返回它应该被插入的位置。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">searchInsert</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (nums[mid] == target) &#123;</span><br><span class="line">                <span class="keyword">return</span> mid;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (nums[mid] &lt; target) &#123;</span><br><span class="line">                left = mid + <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                right = mid - <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 如果没找到，left 就是应该插入的位置</span></span><br><span class="line">        <span class="keyword">return</span> left;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="案例3：搜索旋转排序数组">案例3：搜索旋转排序数组</h3><p><strong>LeetCode 33</strong>：在旋转过的有序数组中查找目标值。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">search</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (nums[mid] == target) &#123;</span><br><span class="line">                <span class="keyword">return</span> mid;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 判断哪半边是有序的</span></span><br><span class="line">            <span class="keyword">if</span> (nums[left] &lt;= nums[mid]) &#123;</span><br><span class="line">                <span class="comment">// 左半边有序</span></span><br><span class="line">                <span class="keyword">if</span> (nums[left] &lt;= target &amp;&amp; target &lt; nums[mid]) &#123;</span><br><span class="line">                    right = mid - <span class="number">1</span>; <span class="comment">// 目标在左半边</span></span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    left = mid + <span class="number">1</span>;  <span class="comment">// 目标在右半边</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 右半边有序</span></span><br><span class="line">                <span class="keyword">if</span> (nums[mid] &lt; target &amp;&amp; target &lt;= nums[right]) &#123;</span><br><span class="line">                    left = mid + <span class="number">1</span>;  <span class="comment">// 目标在右半边</span></span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    right = mid - <span class="number">1</span>; <span class="comment">// 目标在左半边</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="案例4：寻找旋转排序数组中的最小值">案例4：寻找旋转排序数组中的最小值</h3><p><strong>LeetCode 153</strong>：在旋转排序数组中找到最小值。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findMin</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (left &lt; right) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (nums[mid] &gt; nums[right]) &#123;</span><br><span class="line">                <span class="comment">// 最小值在右半部分</span></span><br><span class="line">                left = mid + <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 最小值在左半部分或就是 mid</span></span><br><span class="line">                right = mid;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> nums[left];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="案例5：x-的平方根">案例5：x 的平方根</h3><p><strong>LeetCode 69</strong>：实现 <code>int sqrt(int x)</code> 函数，返回 x 的平方根的整数部分。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">mySqrt</span><span class="params">(<span class="type">int</span> x)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (x == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">1</span>, right = x;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 防止溢出，使用除法而不是乘法</span></span><br><span class="line">            <span class="keyword">if</span> (mid &lt;= x / mid &amp;&amp; (mid + <span class="number">1</span>) &gt; x / (mid + <span class="number">1</span>)) &#123;</span><br><span class="line">                <span class="keyword">return</span> mid;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (mid &gt; x / mid) &#123;</span><br><span class="line">                right = mid - <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                left = mid + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> right;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="案例6：购物系统降级规则">案例6：购物系统降级规则</h3><p><strong>实际应用场景</strong>：购物APP核心系统面临集群故障，需要设计降级规则来限制客户端调用量。</p><p><strong>问题描述</strong>：</p><ul><li>有N个客户端，每个客户端有不同的调用量R = [R₁, R₂, …, Rₙ]</li><li>核心系统最大调用量为cnt</li><li>如果总调用量sum® ≤ cnt，返回-1（正常调用）</li><li>如果总调用量sum® &gt; cnt，需要设定阈值value</li><li>超过value的客户端限制为value，求最大可能的value</li></ul><p><strong>解决方案</strong>：使用二分查找寻找最大阈值</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ShoppingSystemDegradation</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 计算购物系统降级阈值</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> R 各客户端调用量数组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> cnt 系统最大调用量</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 最大阈值value，如果总调用量不超过cnt则返回-1</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findDegradationThreshold</span><span class="params">(<span class="type">int</span>[] R, <span class="type">int</span> cnt)</span> &#123;</span><br><span class="line">        <span class="comment">// 计算总调用量</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">total</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> num : R) &#123;</span><br><span class="line">            total += num;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 如果总调用量不超过限制，返回-1（正常调用）</span></span><br><span class="line">        <span class="keyword">if</span> (total &lt;= cnt) &#123;</span><br><span class="line">            <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 使用二分查找寻找最大阈值value</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = getMax(R);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 计算使用mid作为阈值时的总调用量</span></span><br><span class="line">            <span class="type">long</span> <span class="variable">sumWithThreshold</span> <span class="operator">=</span> calculateSumWithThreshold(R, mid);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (sumWithThreshold &lt;= cnt) &#123;</span><br><span class="line">                <span class="comment">// 当前阈值可以接受，尝试更大的值</span></span><br><span class="line">                left = mid + <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 当前阈值太大，需要减小</span></span><br><span class="line">                right = mid - <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// right就是最大的满足条件的阈值</span></span><br><span class="line">        <span class="keyword">return</span> right;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 计算使用给定阈值时的总调用量</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> <span class="title function_">calculateSumWithThreshold</span><span class="params">(<span class="type">int</span>[] R, <span class="type">int</span> threshold)</span> &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> num : R) &#123;</span><br><span class="line">            sum += Math.min(num, threshold);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> sum;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取数组中的最大值</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="title function_">getMax</span><span class="params">(<span class="type">int</span>[] R)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">max</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> num : R) &#123;</span><br><span class="line">            max = Math.max(max, num);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> max;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">ShoppingSystemDegradation</span> <span class="variable">solution</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ShoppingSystemDegradation</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 测试用例1：需要降级</span></span><br><span class="line">        <span class="type">int</span>[] R1 = &#123;<span class="number">5</span>, <span class="number">3</span>, <span class="number">8</span>, <span class="number">2</span>, <span class="number">7</span>&#125;;</span><br><span class="line">        <span class="type">int</span> <span class="variable">cnt1</span> <span class="operator">=</span> <span class="number">15</span>;</span><br><span class="line">        System.out.println(<span class="string">&quot;测试用例1：&quot;</span> + solution.findDegradationThreshold(R1, cnt1)); <span class="comment">// 输出: 3</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 测试用例2：不需要降级</span></span><br><span class="line">        <span class="type">int</span>[] R2 = &#123;<span class="number">2</span>, <span class="number">3</span>, <span class="number">1</span>, <span class="number">4</span>&#125;;</span><br><span class="line">        <span class="type">int</span> <span class="variable">cnt2</span> <span class="operator">=</span> <span class="number">20</span>;</span><br><span class="line">        System.out.println(<span class="string">&quot;测试用例2：&quot;</span> + solution.findDegradationThreshold(R2, cnt2)); <span class="comment">// 输出: -1</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 测试用例3：边界情况</span></span><br><span class="line">        <span class="type">int</span>[] R3 = &#123;<span class="number">10</span>, <span class="number">10</span>, <span class="number">10</span>&#125;;</span><br><span class="line">        <span class="type">int</span> <span class="variable">cnt3</span> <span class="operator">=</span> <span class="number">25</span>;</span><br><span class="line">        System.out.println(<span class="string">&quot;测试用例3：&quot;</span> + solution.findDegradationThreshold(R3, cnt3)); <span class="comment">// 输出: 8</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>算法分析</strong>：</p><ul><li><strong>时间复杂度</strong>：O(n log m)，其中n是客户端数量，m是最大调用量</li><li><strong>空间复杂度</strong>：O(1)</li><li><strong>核心思想</strong>：通过二分查找快速定位最大阈值，避免线性搜索</li></ul><h2 id="⚡-性能分析">⚡ 性能分析</h2><h3 id="时间复杂度">时间复杂度</h3><ul><li><strong>基础二分查找</strong>：O(log n)</li><li><strong>所有变种形式</strong>：O(log n)</li></ul><h3 id="空间复杂度">空间复杂度</h3><ul><li><strong>迭代实现</strong>：O(1)</li><li><strong>递归实现</strong>：O(log n)（递归栈空间）</li></ul><h3 id="性能对比">性能对比</h3><table><thead><tr><th>算法</th><th>时间复杂度</th><th>空间复杂度</th><th>适用场景</th></tr></thead><tbody><tr><td>线性搜索</td><td>O(n)</td><td>O(1)</td><td>无序数组</td></tr><tr><td>二分查找</td><td>O(log n)</td><td>O(1)</td><td>有序数组</td></tr><tr><td>哈希表查找</td><td>O(1) 平均</td><td>O(n)</td><td>快速查找</td></tr></tbody></table><h2 id="🛠️-实战练习">🛠️ 实战练习</h2><h3 id="练习题1：寻找峰值">练习题1：寻找峰值</h3><p><strong>描述</strong>：峰值元素是指其值严格大于左右相邻值的元素。给定一个数组，找到峰值元素并返回其索引。</p><p><strong>要求</strong>：时间复杂度 O(log n)</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 你的代码实现</span></span><br></pre></td></tr></table></figure><h3 id="练习题2：搜索二维矩阵">练习题2：搜索二维矩阵</h3><p><strong>描述</strong>：编写一个高效的算法来搜索 m × n 矩阵中的一个目标值。该矩阵具有以下特性：</p><ul><li>每行中的整数从左到右按升序排列</li><li>每行的第一个整数大于前一行的最后一个整数</li></ul><p><strong>要求</strong>：时间复杂度 O(log(m × n))</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 你的代码实现</span></span><br></pre></td></tr></table></figure><h3 id="练习题3：寻找重复数">练习题3：寻找重复数</h3><p><strong>描述</strong>：给定一个包含 n + 1 个整数的数组，其数字都在 1 到 n 之间（包括 1 和 n），可知至少存在一个重复的数字。假设只有一个重复的数字，找出这个重复的数字。</p><p><strong>要求</strong>：</p><ul><li>不能修改原数组（假设数组是只读的）</li><li>只能使用额外的 O(1) 的空间</li><li>时间复杂度小于 O(n²)</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 你的代码实现</span></span><br></pre></td></tr></table></figure><h2 id="📚-相关LeetCode题目">📚 相关LeetCode题目</h2><h3 id="基础题目">基础题目</h3><ul><li><a href="https://leetcode.cn/problems/binary-search/">704. 二分查找</a></li><li><a href="https://leetcode.cn/problems/search-insert-position/">35. 搜索插入位置</a></li><li><a href="https://leetcode.cn/problems/sqrtx/">69. x 的平方根</a></li></ul><h3 id="进阶题目">进阶题目</h3><ul><li><a href="https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/">34. 在排序数组中查找元素的第一个和最后一个位置</a></li><li><a href="https://leetcode.cn/problems/search-in-rotated-sorted-array/">33. 搜索旋转排序数组</a></li><li><a href="https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/">153. 寻找旋转排序数组中的最小值</a></li><li><a href="https://leetcode.cn/problems/find-peak-element/">162. 寻找峰值</a></li><li><a href="https://leetcode.cn/problems/search-a-2d-matrix/">74. 搜索二维矩阵</a></li></ul><h3 id="高难度题目">高难度题目</h3><ul><li><a href="https://leetcode.cn/problems/find-the-duplicate-number/">287. 寻找重复数</a></li><li><a href="https://leetcode.cn/problems/median-of-two-sorted-arrays/">4. 寻找两个正序数组的中位数</a></li></ul><h2 id="🎯-关键要点总结">🎯 关键要点总结</h2><h3 id="核心原则">核心原则</h3><ol><li><strong>有序性</strong>：二分查找的前提是数组必须有序</li><li><strong>边界条件</strong>：注意处理 left、right、mid 的边界情况</li><li><strong>溢出处理</strong>：使用 <code>left + (right - left) / 2</code> 而不是 <code>(left + right) / 2</code></li><li><strong>终止条件</strong>：理解 <code>while (left &lt;= right)</code> 和 <code>while (left &lt; right)</code> 的区别</li></ol><h3 id="常见陷阱">常见陷阱</h3><ol><li><strong>死循环</strong>：注意更新 left 和 right 时的边界条件</li><li><strong>数组越界</strong>：检查 mid 的相邻元素是否存在</li><li><strong>精度问题</strong>：处理整数溢出和浮点数比较</li><li><strong>特殊情况</strong>：空数组、单元素数组等边界情况</li></ol><h3 id="优化技巧">优化技巧</h3><ol><li><strong>位运算优化</strong>：使用 <code>mid = left + ((right - left) &gt;&gt; 1)</code> 提高性能</li><li><strong>早停策略</strong>：在某些情况下可以提前终止搜索</li><li><strong>缓存中间结果</strong>：避免重复计算</li></ol><h2 id="📖-参考资料">📖 参考资料</h2><ul><li><a href="https://book.douban.com/subject/1885170/">算法导论（第3版）</a></li><li><a href="https://leetcode.com/tag/binary-search/">LeetCode二分查找标签</a></li><li><a href="https://en.wikipedia.org/wiki/Binary_search_algorithm">二分查找的数学原理</a></li><li><a href="https://docs.oracle.com/javase/tutorial/collections/algorithms/index.html">Java实现二分查找最佳实践</a></li></ul><hr><p><em>最后更新：2025-01-15</em></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;🔍 二分查找算法及变种形式详解&lt;/h1&gt;
&lt;h2 id=&quot;📖-概述&quot;&gt;📖 概述&lt;/h2&gt;
&lt;p&gt;二分查找（Binary Search）是计算机科学中最基础、最重要的算法之一。它通过每次将搜索范围缩小一半的方式，在有序数组中高效地查找目标元素，时间复杂度为 &lt;stro</summary>
      
    
    
    
    <category term="书山指路" scheme="https://blog.adoreorg.cn/categories/%E4%B9%A6%E5%B1%B1%E6%8C%87%E8%B7%AF/"/>
    
    
    <category term="算法" scheme="https://blog.adoreorg.cn/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="数据结构" scheme="https://blog.adoreorg.cn/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    
    <category term="LeetCode" scheme="https://blog.adoreorg.cn/tags/LeetCode/"/>
    
    <category term="ACM" scheme="https://blog.adoreorg.cn/tags/ACM/"/>
    
    <category term="二分查找" scheme="https://blog.adoreorg.cn/tags/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE/"/>
    
  </entry>
  
  <entry>
    <title>单调栈与单调队列算法详解</title>
    <link href="https://blog.adoreorg.cn/posts/sjdwe26k.html"/>
    <id>https://blog.adoreorg.cn/posts/sjdwe26k.html</id>
    <published>2025-01-14T16:00:00.000Z</published>
    <updated>2025-10-07T15:41:58.257Z</updated>
    
    <content type="html"><![CDATA[<h1>单调栈与单调队列：优雅解决数组中的难题</h1><h2 id="🎯-学习目标">🎯 学习目标</h2><ul><li>掌握单调栈和单调队列的核心原理</li><li>理解两种数据结构的实现方式与适用场景</li><li>能够熟练应用单调栈/队列解决实际算法问题</li><li>了解常见的优化技巧与经典应用</li></ul><h2 id="📑-目录">📑 目录</h2><ul><li><a href="#%F0%9F%93%84-%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5">核心概念</a></li><li><a href="#%F0%9F%9A%80-%E5%8D%95%E8%B0%83%E6%A0%88">单调栈</a></li><li><a href="#%F0%9F%8C%8A-%E5%8D%95%E8%B0%83%E9%98%9F%E5%88%97">单调队列</a></li><li><a href="#%F0%9F%94%8D-%E5%AF%B9%E6%AF%94%E4%B8%8E%E9%80%89%E6%8B%A9%E7%AD%96%E7%95%A5">对比与选择策略</a></li><li><a href="#%F0%9F%8F%97%EF%B8%8F-%E5%AE%9E%E6%88%98%E5%BA%94%E7%94%A8%E6%A1%88%E4%BE%8B">实战应用案例</a></li><li><a href="#%F0%9F%9A%A1-%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90">性能分析</a></li><li><a href="#%F0%9F%8E%AF-%E7%BB%8F%E5%85%B8leetcode%E9%A2%98%E7%9B%AE">经典LeetCode题目</a></li><li><a href="#%F0%9F%A7%A0-%E8%AE%B0%E5%BF%86%E5%8F%A3%E8%AF%80%E4%B8%8E%E6%8A%80%E5%B7%A7">记忆口诀与技巧</a></li><li><a href="#%F0%9F%8E%93-%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93">学习总结</a></li></ul><h2 id="📁-核心概念">📁 核心概念</h2><h3 id="什么是单调数据结构？">什么是单调数据结构？</h3><p><strong>单调数据结构</strong>是一种特殊的数据结构，其内部元素保持特定的单调性（递增或递减）。在算法问题中，单调数据结构常用于优化时间复杂度，特别是在处理数组中的<strong>next greater element</strong>、<strong>滑动窗口最大值</strong>等问题时表现出色。</p><p>最常见的单调数据结构有：</p><ul><li><strong>单调栈</strong>：栈中元素保持单调递增或递减</li><li><strong>单调队列</strong>：队列中元素保持单调递增或递减</li></ul><h2 id="🚀-单调栈">🚀 单调栈</h2><h3 id="🎯-核心思想">🎯 核心思想</h3><p><strong>单调栈</strong>是一种特殊的栈，其中元素按照某种单调性（递增或递减）排列。它的关键特点是：每当新元素入栈时，会弹出栈中所有破坏单调性的元素，然后再入栈。</p><p>这种特性使得单调栈非常适合解决<strong>下一个更大元素</strong>、<strong>前一个更小元素</strong>等问题。</p><h3 id="🎨-算法流程">🎨 算法流程</h3><p>以<strong>单调递减栈</strong>为例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">🚀 单调递减栈的基本操作：</span><br><span class="line">1. 初始化一个空栈</span><br><span class="line">2. 遍历数组中的每个元素：</span><br><span class="line">   a. 当栈不为空且栈顶元素小于当前元素时，弹出栈顶元素</span><br><span class="line">   b. 处理栈顶元素与当前元素的关系</span><br><span class="line">   c. 将当前元素入栈</span><br><span class="line">3. 处理栈中剩余的元素（可选）</span><br></pre></td></tr></table></figure><h3 id="💻-Java实现">💻 Java实现</h3><h4 id="1-基础单调栈实现">1. 基础单调栈实现</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Stack;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🚀 单调栈的基础实现</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MonotonicStack</span> &#123;</span><br><span class="line">    <span class="comment">// 单调递减栈（栈顶元素为最大）</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">monotonicDecreasingStack</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">        Stack&lt;Integer&gt; stack = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; nums.length; i++) &#123;</span><br><span class="line">            <span class="comment">// 当栈不为空且栈顶元素小于当前元素时，弹出栈顶元素</span></span><br><span class="line">            <span class="keyword">while</span> (!stack.isEmpty() &amp;&amp; stack.peek() &lt; nums[i]) &#123;</span><br><span class="line">                <span class="comment">// 此时nums[i]是栈顶元素的下一个更大元素</span></span><br><span class="line">                <span class="type">int</span> <span class="variable">popped</span> <span class="operator">=</span> stack.pop();</span><br><span class="line">                System.out.println(<span class="string">&quot;元素 &quot;</span> + popped + <span class="string">&quot; 的下一个更大元素是: &quot;</span> + nums[i]);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 将当前元素入栈</span></span><br><span class="line">            stack.push(nums[i]);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 处理栈中剩余元素（没有下一个更大元素）</span></span><br><span class="line">        <span class="keyword">while</span> (!stack.isEmpty()) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">popped</span> <span class="operator">=</span> stack.pop();</span><br><span class="line">            System.out.println(<span class="string">&quot;元素 &quot;</span> + popped + <span class="string">&quot; 没有下一个更大元素&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 单调递增栈（栈顶元素为最小）</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">monotonicIncreasingStack</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">        Stack&lt;Integer&gt; stack = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; nums.length; i++) &#123;</span><br><span class="line">            <span class="comment">// 当栈不为空且栈顶元素大于当前元素时，弹出栈顶元素</span></span><br><span class="line">            <span class="keyword">while</span> (!stack.isEmpty() &amp;&amp; stack.peek() &gt; nums[i]) &#123;</span><br><span class="line">                <span class="comment">// 此时nums[i]是栈顶元素的下一个更小元素</span></span><br><span class="line">                <span class="type">int</span> <span class="variable">popped</span> <span class="operator">=</span> stack.pop();</span><br><span class="line">                System.out.println(<span class="string">&quot;元素 &quot;</span> + popped + <span class="string">&quot; 的下一个更小元素是: &quot;</span> + nums[i]);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 将当前元素入栈</span></span><br><span class="line">            stack.push(nums[i]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-单调栈应用：下一个更大元素">2. 单调栈应用：下一个更大元素</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"><span class="keyword">import</span> java.util.HashMap;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"><span class="keyword">import</span> java.util.Stack;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🚀 下一个更大元素问题（LeetCode 496）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NextGreaterElement</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 计算数组中每个元素的下一个更大元素</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> nums 输入数组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 每个元素的下一个更大元素数组，没有则为-1</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span>[] nextGreaterElement(<span class="type">int</span>[] nums) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> nums.length;</span><br><span class="line">        <span class="type">int</span>[] result = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">        Arrays.fill(result, -<span class="number">1</span>); <span class="comment">// 初始化为-1，表示没有下一个更大元素</span></span><br><span class="line">        Stack&lt;Integer&gt; stack = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;(); <span class="comment">// 存储索引，而不是值</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">            <span class="comment">// 当前元素大于栈顶索引对应的元素时，找到下一个更大元素</span></span><br><span class="line">            <span class="keyword">while</span> (!stack.isEmpty() &amp;&amp; nums[i] &gt; nums[stack.peek()]) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> stack.pop();</span><br><span class="line">                result[index] = nums[i];</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 将当前索引入栈</span></span><br><span class="line">            stack.push(i);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="🌊-单调队列">🌊 单调队列</h2><h3 id="🎯-核心思想-2">🎯 核心思想</h3><p><strong>单调队列</strong>是一种特殊的队列，其中元素按照某种单调性（递增或递减）排列。与单调栈不同，单调队列支持两端操作：在队尾添加元素，在队头移除元素。</p><p>单调队列常用于解决<strong>滑动窗口最大值/最小值</strong>等问题，可以将时间复杂度从O(nk)优化到O(n)。</p><h3 id="🎨-算法流程-2">🎨 算法流程</h3><p>以<strong>单调递减队列</strong>（维护窗口最大值）为例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">🌊 单调递减队列处理滑动窗口最大值：</span><br><span class="line">1. 初始化一个双端队列和结果数组</span><br><span class="line">2. 遍历数组中的每个元素：</span><br><span class="line">   a. 移除队列中所有小于当前元素的值（保持单调性）</span><br><span class="line">   b. 将当前元素的索引加入队列</span><br><span class="line">   c. 移除队列中超出窗口范围的元素（队头）</span><br><span class="line">   d. 当窗口形成时，记录队头元素为当前窗口的最大值</span><br><span class="line">3. 返回结果数组</span><br></pre></td></tr></table></figure><h3 id="💻-Java实现-2">💻 Java实现</h3><h4 id="1-单调队列的实现">1. 单调队列的实现</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Deque;</span><br><span class="line"><span class="keyword">import</span> java.util.LinkedList;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🌊 单调队列的实现</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MonotonicQueue</span> &#123;</span><br><span class="line">    <span class="comment">// 双端队列，用于实现单调队列</span></span><br><span class="line">    <span class="keyword">private</span> Deque&lt;Integer&gt; deque = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 入队操作，保持队列单调递减</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">push</span><span class="params">(<span class="type">int</span> value)</span> &#123;</span><br><span class="line">        <span class="comment">// 移除队列中所有小于当前元素的值</span></span><br><span class="line">        <span class="keyword">while</span> (!deque.isEmpty() &amp;&amp; deque.peekLast() &lt; value) &#123;</span><br><span class="line">            deque.pollLast();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 将当前元素加入队列</span></span><br><span class="line">        deque.offerLast(value);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 出队操作（如果队头元素等于目标值）</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">pop</span><span class="params">(<span class="type">int</span> value)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!deque.isEmpty() &amp;&amp; deque.peekFirst() == value) &#123;</span><br><span class="line">            deque.pollFirst();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取队列中的最大值（队头元素）</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">max</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> deque.peekFirst();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-单调队列应用：滑动窗口最大值">2. 单调队列应用：滑动窗口最大值</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"><span class="keyword">import</span> java.util.Deque;</span><br><span class="line"><span class="keyword">import</span> java.util.LinkedList;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🌊 滑动窗口最大值问题（LeetCode 239）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SlidingWindowMaximum</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 计算滑动窗口中的最大值</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> nums 输入数组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> k 窗口大小</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 每个窗口的最大值组成的数组</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span>[] maxSlidingWindow(<span class="type">int</span>[] nums, <span class="type">int</span> k) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> nums.length;</span><br><span class="line">        <span class="keyword">if</span> (n == <span class="number">0</span> || k == <span class="number">0</span>) <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">0</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span>[] result = <span class="keyword">new</span> <span class="title class_">int</span>[n - k + <span class="number">1</span>];</span><br><span class="line">        Deque&lt;Integer&gt; deque = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;(); <span class="comment">// 存储索引</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">            <span class="comment">// 1. 移除队列中超出窗口范围的元素（队头）</span></span><br><span class="line">            <span class="keyword">while</span> (!deque.isEmpty() &amp;&amp; deque.peekFirst() &lt; i - k + <span class="number">1</span>) &#123;</span><br><span class="line">                deque.pollFirst();</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 2. 移除队列中所有小于当前元素的值（保持单调性）</span></span><br><span class="line">            <span class="keyword">while</span> (!deque.isEmpty() &amp;&amp; nums[deque.peekLast()] &lt; nums[i]) &#123;</span><br><span class="line">                deque.pollLast();</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 3. 将当前索引加入队列</span></span><br><span class="line">            deque.offerLast(i);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 4. 当窗口形成时，记录队头元素为当前窗口的最大值</span></span><br><span class="line">            <span class="keyword">if</span> (i &gt;= k - <span class="number">1</span>) &#123;</span><br><span class="line">                result[i - k + <span class="number">1</span>] = nums[deque.peekFirst()];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="🔍-对比与选择策略">🔍 对比与选择策略</h2><h3 id="📊-核心区别对比表">📊 核心区别对比表</h3><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><tr><td><strong>时间复杂度</strong></td><td>O(n)</td><td>O(n)</td></tr><tr><td><strong>空间复杂度</strong></td><td>O(n)</td><td>O(k)，k为窗口大小</td></tr><tr><td><strong>优化场景</strong></td><td>一维数组中的Next Greater问题</td><td>滑动窗口问题</td></tr></tbody></table><h3 id="🎯-选择策略">🎯 选择策略</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">🤔 什么时候用单调栈？</span><br><span class="line">✅ 需要找元素的下一个更大/更小元素</span><br><span class="line">✅ 处理直方图相关问题</span><br><span class="line">✅ 解决括号匹配、区间合并等问题</span><br><span class="line">✅ 优化动态规划的状态转移</span><br><span class="line"></span><br><span class="line">🤔 什么时候用单调队列？</span><br><span class="line">✅ 需要处理滑动窗口最大值/最小值</span><br><span class="line">✅ 维护动态的极值集合</span><br><span class="line">✅ 需要在两端进行操作的场景</span><br></pre></td></tr></table></figure><h3 id="📋-单调栈对比总结">📋 单调栈对比总结</h3><table><thead><tr><th>栈类型</th><th>单调性</th><th>弹出条件</th><th>解决的问题</th><th>遍历方向</th></tr></thead><tbody><tr><td>递增栈</td><td>栈底 → 栈顶 ↑</td><td>当前元素 ≤ 栈顶</td><td>右侧第一个更小元素</td><td>从右向左</td></tr><tr><td>递减栈</td><td>栈底 → 栈顶 ↓</td><td>当前元素 ≥ 栈顶</td><td>右侧第一个更大元素</td><td>从右向左</td></tr></tbody></table><h3 id="💡-记忆技巧">💡 记忆技巧</h3><p><strong>单调性方向</strong>：</p><ul><li>递增栈 → 找更小元素（栈顶是最近的更小值）</li><li>递减栈 → 找更大元素（栈顶是最近的更大值）</li></ul><p><strong>遍历方向</strong>：</p><ul><li>从右向左遍历时，栈中存储的是<strong>右侧</strong>的信息</li><li>从左向右遍历时，栈中存储的是<strong>左侧</strong>的信息（需调整逻辑）</li></ul><p><strong>边界处理</strong>：</p><ul><li>栈为空时，表示无解（通常用 -1 或特殊值标记）</li></ul><h2 id="🏗️-实战应用案例">🏗️ 实战应用案例</h2><h3 id="🎯-案例1：柱状图中最大的矩形（LeetCode-84）">🎯 案例1：柱状图中最大的矩形（LeetCode 84）</h3><p><strong>问题描述</strong>：给定 n 个非负整数，用来表示柱状图中各个柱子的高度。每个柱子彼此相邻，且宽度为 1。求在该柱状图中，能够勾勒出来的矩形的最大面积。</p><p><strong>单调栈解决方案</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Stack;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 📊 柱状图中最大的矩形（LeetCode 84）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LargestRectangleInHistogram</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 计算柱状图中最大的矩形面积</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> heights 柱子高度数组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 最大矩形面积</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">largestRectangleArea</span><span class="params">(<span class="type">int</span>[] heights)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> heights.length;</span><br><span class="line">        <span class="keyword">if</span> (n == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        Stack&lt;Integer&gt; stack = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();</span><br><span class="line">        <span class="type">int</span> <span class="variable">maxArea</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 在数组末尾添加一个0，确保栈中所有元素都能被处理</span></span><br><span class="line">        <span class="type">int</span>[] extendedHeights = <span class="keyword">new</span> <span class="title class_">int</span>[n + <span class="number">1</span>];</span><br><span class="line">        System.arraycopy(heights, <span class="number">0</span>, extendedHeights, <span class="number">0</span>, n);</span><br><span class="line">        extendedHeights[n] = <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt;= n; i++) &#123;</span><br><span class="line">            <span class="comment">// 当前高度小于栈顶高度时，计算栈顶高度对应的矩形面积</span></span><br><span class="line">            <span class="keyword">while</span> (!stack.isEmpty() &amp;&amp; extendedHeights[i] &lt; extendedHeights[stack.peek()]) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">h</span> <span class="operator">=</span> extendedHeights[stack.pop()]; <span class="comment">// 当前柱子高度</span></span><br><span class="line">                <span class="type">int</span> <span class="variable">w</span> <span class="operator">=</span> stack.isEmpty() ? i : i - stack.peek() - <span class="number">1</span>; <span class="comment">// 宽度</span></span><br><span class="line">                maxArea = Math.max(maxArea, h * w);</span><br><span class="line">            &#125;</span><br><span class="line">            stack.push(i);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> maxArea;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="🎯-案例2：接雨水（LeetCode-42）">🎯 案例2：接雨水（LeetCode 42）</h3><p><strong>问题描述</strong>：给定 n 个非负整数表示每个宽度为 1 的柱子的高度图，计算按此排列的柱子，下雨之后能接多少雨水。</p><p><strong>单调栈解决方案</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Stack;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 💧 接雨水问题（LeetCode 42）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TrappingRainWater</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 计算能接的雨水总量</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> height 柱子高度数组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 能接的雨水总量</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">trap</span><span class="params">(<span class="type">int</span>[] height)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> height.length;</span><br><span class="line">        <span class="keyword">if</span> (n == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        Stack&lt;Integer&gt; stack = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();</span><br><span class="line">        <span class="type">int</span> <span class="variable">totalWater</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">            <span class="comment">// 当前高度大于栈顶高度时，形成凹槽，可以接雨水</span></span><br><span class="line">            <span class="keyword">while</span> (!stack.isEmpty() &amp;&amp; height[i] &gt; height[stack.peek()]) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">bottom</span> <span class="operator">=</span> stack.pop(); <span class="comment">// 凹槽底部位置</span></span><br><span class="line">                </span><br><span class="line">                <span class="keyword">if</span> (stack.isEmpty()) <span class="keyword">break</span>; <span class="comment">// 没有左边界，无法接雨水</span></span><br><span class="line">                </span><br><span class="line">                <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> stack.peek(); <span class="comment">// 左边界位置</span></span><br><span class="line">                <span class="type">int</span> <span class="variable">width</span> <span class="operator">=</span> i - left - <span class="number">1</span>; <span class="comment">// 宽度</span></span><br><span class="line">                <span class="type">int</span> <span class="variable">h</span> <span class="operator">=</span> Math.min(height[left], height[i]) - height[bottom]; <span class="comment">// 高度</span></span><br><span class="line">                totalWater += width * h;</span><br><span class="line">            &#125;</span><br><span class="line">            stack.push(i);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> totalWater;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="⚡-性能分析">⚡ 性能分析</h2><h3 id="📊-时间复杂度对比">📊 时间复杂度对比</h3><table><thead><tr><th>算法</th><th>朴素方法</th><th>使用单调数据结构</th><th>优化倍数</th></tr></thead><tbody><tr><td>下一个更大元素</td><td>O(n²)</td><td>O(n)</td><td>n倍</td></tr><tr><td>滑动窗口最大值</td><td>O(nk)</td><td>O(n)</td><td>k倍</td></tr><tr><td>柱状图中最大矩形</td><td>O(n²)</td><td>O(n)</td><td>n倍</td></tr><tr><td>接雨水</td><td>O(n²)</td><td>O(n)</td><td>n倍</td></tr></tbody></table><h3 id="🚀-优化原理">🚀 优化原理</h3><p>单调数据结构的核心优化原理是：<strong>利用单调性避免不必要的比较</strong>。在处理数组问题时，很多情况下我们不需要与所有元素进行比较，只需要关注那些可能成为&quot;下一个更大元素&quot;或&quot;窗口最大值&quot;的元素。</p><p>通过维护单调的结构，我们可以在O(n)的时间复杂度内解决很多看似需要O(n²)时间的问题。</p><h2 id="🎯-经典LeetCode题目">🎯 经典LeetCode题目</h2><h3 id="📝-基础题目（⭐⭐☆☆☆）">📝 基础题目（⭐⭐☆☆☆）</h3><table><thead><tr><th>题号</th><th>题目名称</th><th>算法类型</th><th>难度</th></tr></thead><tbody><tr><td>496</td><td>下一个更大元素 I</td><td>单调栈</td><td>⭐⭐☆☆☆</td></tr><tr><td>503</td><td>下一个更大元素 II</td><td>单调栈</td><td>⭐⭐☆☆☆</td></tr><tr><td>239</td><td>滑动窗口最大值</td><td>单调队列</td><td>⭐⭐⭐☆☆</td></tr><tr><td>844</td><td>比较含退格的字符串</td><td>单调栈</td><td>⭐⭐☆☆☆</td></tr></tbody></table><h3 id="📝-进阶题目（⭐⭐⭐☆☆）">📝 进阶题目（⭐⭐⭐☆☆）</h3><table><thead><tr><th>题号</th><th>题目名称</th><th>算法类型</th><th>难度</th></tr></thead><tbody><tr><td>84</td><td>柱状图中最大的矩形</td><td>单调栈</td><td>⭐⭐⭐☆☆</td></tr><tr><td>42</td><td>接雨水</td><td>单调栈</td><td>⭐⭐⭐☆☆</td></tr><tr><td>901</td><td>股票价格跨度</td><td>单调栈</td><td>⭐⭐⭐☆☆</td></tr><tr><td>739</td><td>每日温度</td><td>单调栈</td><td>⭐⭐⭐☆☆</td></tr></tbody></table><h3 id="📝-高难度题目（⭐⭐⭐⭐☆）">📝 高难度题目（⭐⭐⭐⭐☆）</h3><table><thead><tr><th>题号</th><th>题目名称</th><th>算法类型</th><th>难度</th></tr></thead><tbody><tr><td>316</td><td>去除重复字母</td><td>单调栈 + 贪心</td><td>⭐⭐⭐⭐☆</td></tr><tr><td>402</td><td>移掉K位数字</td><td>单调栈 + 贪心</td><td>⭐⭐⭐⭐☆</td></tr><tr><td>85</td><td>最大矩形</td><td>单调栈</td><td>⭐⭐⭐⭐☆</td></tr><tr><td>239</td><td>滑动窗口最大值（hard版）</td><td>单调队列</td><td>⭐⭐⭐⭐☆</td></tr></tbody></table><h2 id="🧠-记忆口诀与技巧">🧠 记忆口诀与技巧</h2><h3 id="🎯-单调栈记忆口诀">🎯 单调栈记忆口诀</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">🚀 单调栈四步走：</span><br><span class="line">1️⃣ 维护栈的单调性（弹出破坏规则的元素）</span><br><span class="line">2️⃣ 处理弹出元素的关系（找到next greater/smaller）</span><br><span class="line">3️⃣ 当前元素入栈（保持结构）</span><br><span class="line">4️⃣ 清理栈中剩余元素（可选）</span><br><span class="line"></span><br><span class="line">💡 单调栈精髓：</span><br><span class="line">&quot;后进先出保单调，遇到破坏就弹出，利用特性找关系，时间复杂降为O(n)&quot;</span><br></pre></td></tr></table></figure><h3 id="🎯-单调队列记忆口诀">🎯 单调队列记忆口诀</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">🌊 单调队列四步曲：</span><br><span class="line">1️⃣ 清理过期元素（队头超出窗口）</span><br><span class="line">2️⃣ 维护队列单调性（弹出队尾小于当前的元素）</span><br><span class="line">3️⃣ 当前元素入队（从队尾）</span><br><span class="line">4️⃣ 记录窗口极值（队头元素）</span><br><span class="line"></span><br><span class="line">💡 单调队列精髓：</span><br><span class="line">&quot;双端操作维护单调，滑动窗口极值显，队头永远是最优，O(n)时间效率高&quot;</span><br></pre></td></tr></table></figure><h2 id="🎓-学习总结">🎓 学习总结</h2><h3 id="🎯-核心要点回顾">🎯 核心要点回顾</h3><ol><li><strong>单调栈</strong>是解决 next greater/smaller 元素问题的利器，时间复杂度O(n)</li><li><strong>单调队列</strong>特别适合处理滑动窗口最大值/最小值问题，同样O(n)时间复杂度</li><li>两种数据结构都通过维护单调性来避免不必要的比较，优化算法效率</li><li>实现时需要注意栈/队列中存储的是元素值还是索引，这取决于具体问题</li></ol><h3 id="🚀-进阶学习路线">🚀 进阶学习路线</h3><ol><li><strong>📚 基础巩固</strong>：熟练掌握单调栈和单调队列的基本模板</li><li><strong>⚡ 技巧提升</strong>：学习结合贪心、动态规划等算法的综合应用</li><li><strong>🏗️ 实战应用</strong>：解决更多LeetCode相关题目，培养算法直觉</li><li><strong>🔬 算法扩展</strong>：了解更复杂的数据结构，如单调堆、线段树等</li></ol><h3 id="💡-学习建议">💡 学习建议</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">📝 建议1：多画图理解单调数据结构的工作原理</span><br><span class="line">📝 建议2：总结经典问题的模板代码，做到融会贯通</span><br><span class="line">📝 建议3：通过练习不同难度的题目，深化对算法的理解</span><br><span class="line">📝 建议4：注意分析问题特性，选择合适的单调数据结构</span><br><span class="line">📝 建议5：关注边界条件的处理，避免出现数组越界等问题</span><br></pre></td></tr></table></figure><hr><p><em>最后更新：2025-01-16</em><br><em>作者：算法学习小分队</em></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;单调栈与单调队列：优雅解决数组中的难题&lt;/h1&gt;
&lt;h2 id=&quot;🎯-学习目标&quot;&gt;🎯 学习目标&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;掌握单调栈和单调队列的核心原理&lt;/li&gt;
&lt;li&gt;理解两种数据结构的实现方式与适用场景&lt;/li&gt;
&lt;li&gt;能够熟练应用单调栈/队列解决实际算法问</summary>
      
    
    
    
    <category term="书山指路" scheme="https://blog.adoreorg.cn/categories/%E4%B9%A6%E5%B1%B1%E6%8C%87%E8%B7%AF/"/>
    
    
    <category term="算法" scheme="https://blog.adoreorg.cn/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="数据结构" scheme="https://blog.adoreorg.cn/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    
    <category term="LeetCode" scheme="https://blog.adoreorg.cn/tags/LeetCode/"/>
    
    <category term="ACM" scheme="https://blog.adoreorg.cn/tags/ACM/"/>
    
    <category term="单调栈" scheme="https://blog.adoreorg.cn/tags/%E5%8D%95%E8%B0%83%E6%A0%88/"/>
    
    <category term="单调队列" scheme="https://blog.adoreorg.cn/tags/%E5%8D%95%E8%B0%83%E9%98%9F%E5%88%97/"/>
    
    <category term="栈" scheme="https://blog.adoreorg.cn/tags/%E6%A0%88/"/>
    
    <category term="队列" scheme="https://blog.adoreorg.cn/tags/%E9%98%9F%E5%88%97/"/>
    
  </entry>
  
  <entry>
    <title>图的深度优先搜索(DFS)与广度优先搜索(BFS)算法详解</title>
    <link href="https://blog.adoreorg.cn/posts/ds43gf4l.html"/>
    <id>https://blog.adoreorg.cn/posts/ds43gf4l.html</id>
    <published>2025-01-14T16:00:00.000Z</published>
    <updated>2025-10-08T03:14:45.126Z</updated>
    
    <content type="html"><![CDATA[<h1>🌐 图的深度优先搜索(DFS)与广度优先搜索(BFS)算法详解</h1><blockquote><p>🎯 <strong>学习目标</strong>：掌握图的两种基本遍历算法，理解其原理、区别与实战应用</p><p>⭐ <strong>难度等级</strong>：⭐⭐☆☆☆（中等）</p><p>🕐 <strong>预计学习时长</strong>：30-45分钟</p></blockquote><hr><h2 id="📚-目录">📚 目录</h2><ul><li><a href="#-%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5">🎯 核心概念</a></li><li><a href="#-%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2dfs">🌊 深度优先搜索(DFS)</a></li><li><a href="#-%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2bfs">🌊 广度优先搜索(BFS)</a></li><li><a href="#-dfs-vs-bfs%E5%AF%B9%E6%AF%94%E5%88%86%E6%9E%90">🔍 DFS vs BFS对比分析</a></li><li><a href="#-%E5%AE%9E%E6%88%98%E5%BA%94%E7%94%A8%E6%A1%88%E4%BE%8B">🏗️ 实战应用案例</a></li><li><a href="#-%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E4%B8%8E%E4%BC%98%E5%8C%96">⚡ 性能分析与优化</a></li><li><a href="#-%E7%BB%8F%E5%85%B8leetcode%E9%A2%98%E7%9B%AE">🎯 经典LeetCode题目</a></li><li><a href="#-%E8%AE%B0%E5%BF%86%E5%8F%A3%E8%AF%80%E4%B8%8E%E6%8A%80%E5%B7%A7">🧠 记忆口诀与技巧</a></li><li><a href="#-%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99">📖 参考资料</a></li></ul><hr><h2 id="🎯-核心概念">🎯 核心概念</h2><h3 id="🌳-什么是图遍历？">🌳 什么是图遍历？</h3><p>图遍历是指<strong>按照某种规则访问图中所有顶点的过程</strong>，是图算法的基础。就像探索一座城市，你需要决定：</p><ul><li>🚶‍♂️ <strong>深度优先</strong>：先深入一条街道到底，再回头探索其他街道</li><li>🗺️ <strong>广度优先</strong>：先探索当前位置周围的所有街道，再逐步向外扩展</li></ul><h3 id="🎨-图的基本表示">🎨 图的基本表示</h3><p>在Java中，我们通常使用<strong>邻接表</strong>来表示图：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 🎯 图的邻接表表示</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Graph</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> V;                    <span class="comment">// 顶点数量</span></span><br><span class="line">    <span class="keyword">private</span> LinkedList&lt;Integer&gt;[] adj; <span class="comment">// 邻接表</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Graph</span><span class="params">(<span class="type">int</span> v)</span> &#123;</span><br><span class="line">        V = v;</span><br><span class="line">        adj = <span class="keyword">new</span> <span class="title class_">LinkedList</span>[v];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; v; ++i) &#123;</span><br><span class="line">            adj[i] = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 🔄 添加边（无向图）</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addEdge</span><span class="params">(<span class="type">int</span> v, <span class="type">int</span> w)</span> &#123;</span><br><span class="line">        adj[v].add(w);</span><br><span class="line">        adj[w].add(v);  <span class="comment">// 无向图需要添加双向边</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="🌊-深度优先搜索-DFS">🌊 深度优先搜索(DFS)</h2><h3 id="🎯-核心思想">🎯 核心思想</h3><p><strong>深度优先搜索</strong>就像走迷宫，选择一条路一直走到底，遇到死胡同就回退到上一个路口，选择另一条路继续探索。</p><h3 id="🎨-算法流程">🎨 算法流程</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">🚀 DFS遍历流程：</span><br><span class="line">1. 从起始顶点开始</span><br><span class="line">2. 标记当前顶点为已访问</span><br><span class="line">3. 递归访问所有未访问的邻接顶点</span><br><span class="line">4. 当没有未访问的邻接顶点时，回退到上一层</span><br></pre></td></tr></table></figure><h3 id="💻-Java实现">💻 Java实现</h3><h4 id="递归实现（最直观）">递归实现（最直观）</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🌊 深度优先搜索 - 递归实现</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> graph 图的邻接表表示</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> v 起始顶点</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> visited 访问标记数组</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">dfsRecursive</span><span class="params">(LinkedList&lt;Integer&gt;[] graph, <span class="type">int</span> v, <span class="type">boolean</span>[] visited)</span> &#123;</span><br><span class="line">    <span class="comment">// ✅ 1. 标记当前顶点为已访问</span></span><br><span class="line">    visited[v] = <span class="literal">true</span>;</span><br><span class="line">    System.out.print(v + <span class="string">&quot; &quot;</span>);  <span class="comment">// 📝 处理当前顶点</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 🔄 2. 递归访问所有未访问的邻接顶点</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> neighbor : graph[v]) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!visited[neighbor]) &#123;  <span class="comment">// ❌ 只访问未访问的顶点</span></span><br><span class="line">            dfsRecursive(graph, neighbor, visited);  <span class="comment">// 🚀 递归深入</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ↩️ 3. 函数自然返回，相当于回退到上一层</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="迭代实现（使用栈）">迭代实现（使用栈）</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🌊 深度优先搜索 - 迭代实现（栈）</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> graph 图的邻接表表示</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> start 起始顶点</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">dfsIterative</span><span class="params">(LinkedList&lt;Integer&gt;[] graph, <span class="type">int</span> start)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">V</span> <span class="operator">=</span> graph.length;</span><br><span class="line">    <span class="type">boolean</span>[] visited = <span class="keyword">new</span> <span class="title class_">boolean</span>[V];</span><br><span class="line">    Stack&lt;Integer&gt; stack = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 🚀 1. 起始顶点入栈</span></span><br><span class="line">    stack.push(start);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 🔄 2. 当栈不为空时继续遍历</span></span><br><span class="line">    <span class="keyword">while</span> (!stack.isEmpty()) &#123;</span><br><span class="line">        <span class="comment">// 📋 2.1 弹出栈顶元素</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">v</span> <span class="operator">=</span> stack.pop();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ❌ 2.2 如果已经访问过，跳过</span></span><br><span class="line">        <span class="keyword">if</span> (visited[v]) <span class="keyword">continue</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ✅ 2.3 标记为已访问并处理</span></span><br><span class="line">        visited[v] = <span class="literal">true</span>;</span><br><span class="line">        System.out.print(v + <span class="string">&quot; &quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔄 2.4 将所有未访问的邻接顶点入栈</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> neighbor : graph[v]) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!visited[neighbor]) &#123;</span><br><span class="line">                stack.push(neighbor);  <span class="comment">// 📝 注意：栈是后进先出</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="二维数组实现">二维数组实现</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🌊 深度优先搜索实现（二维网格）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DFS</span> &#123;</span><br><span class="line">    <span class="comment">// 定义四个方向：右、下、左、上</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span>[][] dir = &#123;&#123;<span class="number">0</span>, <span class="number">1</span>&#125;, &#123;<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">0</span>, -<span class="number">1</span>&#125;, &#123;-<span class="number">1</span>, <span class="number">0</span>&#125;&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🌊 深度优先搜索主函数</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> grid 二维网格数组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> visited 访问标记数组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> x 起始x坐标</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> y 起始y坐标</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">dfs</span><span class="params">(<span class="type">char</span>[][] grid, <span class="type">boolean</span>[][] visited, <span class="type">int</span> x, <span class="type">int</span> y)</span> &#123;</span><br><span class="line">        <span class="comment">// ✅ 标记当前节点为已访问</span></span><br><span class="line">        visited[x][y] = <span class="literal">true</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔄 遍历四个方向</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">4</span>; i++) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">nextX</span> <span class="operator">=</span> x + dir[i][<span class="number">0</span>];</span><br><span class="line">            <span class="type">int</span> <span class="variable">nextY</span> <span class="operator">=</span> y + dir[i][<span class="number">1</span>];</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 🛑 检查坐标是否越界</span></span><br><span class="line">            <span class="keyword">if</span> (nextX &lt; <span class="number">0</span> || nextX &gt;= grid.length || nextY &lt; <span class="number">0</span> || nextY &gt;= grid[<span class="number">0</span>].length) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// ❌ 跳过已访问的节点</span></span><br><span class="line">            <span class="keyword">if</span> (!visited[nextX][nextY]) &#123;</span><br><span class="line">                <span class="comment">// 🚀 递归访问下一个节点</span></span><br><span class="line">                dfs(grid, visited, nextX, nextY);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🌊 深度优先搜索 - 迭代实现（使用栈）</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> grid 二维网格数组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> visited 访问标记数组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> x 起始x坐标</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> y 起始y坐标</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">dfsIterative</span><span class="params">(<span class="type">char</span>[][] grid, <span class="type">boolean</span>[][] visited, <span class="type">int</span> x, <span class="type">int</span> y)</span> &#123;</span><br><span class="line">        <span class="comment">// 📋 使用栈代替递归</span></span><br><span class="line">        LinkedList&lt;<span class="type">int</span>[]&gt; stack = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">        stack.push(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;x, y&#125;);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (!stack.isEmpty()) &#123;</span><br><span class="line">            <span class="type">int</span>[] cur = stack.pop();</span><br><span class="line">            <span class="type">int</span> <span class="variable">curX</span> <span class="operator">=</span> cur[<span class="number">0</span>];</span><br><span class="line">            <span class="type">int</span> <span class="variable">curY</span> <span class="operator">=</span> cur[<span class="number">1</span>];</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// ❌ 如果已经访问过，跳过</span></span><br><span class="line">            <span class="keyword">if</span> (visited[curX][curY]) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// ✅ 标记为已访问</span></span><br><span class="line">            visited[curX][curY] = <span class="literal">true</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 🔄 遍历四个方向（注意：栈是后进先出，所以反向入栈）</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">3</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">nextX</span> <span class="operator">=</span> curX + dir[i][<span class="number">0</span>];</span><br><span class="line">                <span class="type">int</span> <span class="variable">nextY</span> <span class="operator">=</span> curY + dir[i][<span class="number">1</span>];</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 🛑 检查边界和访问状态</span></span><br><span class="line">                <span class="keyword">if</span> (nextX &gt;= <span class="number">0</span> &amp;&amp; nextX &lt; grid.length &amp;&amp; nextY &gt;= <span class="number">0</span> &amp;&amp; nextY &lt; grid[<span class="number">0</span>].length &amp;&amp; !visited[nextX][nextY]) &#123;</span><br><span class="line">                    stack.push(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;nextX, nextY&#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="🎨-DFS遍历示例">🎨 DFS遍历示例</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">图结构：0-1-2</span><br><span class="line">         |</span><br><span class="line">         3-4</span><br><span class="line"></span><br><span class="line">DFS遍历过程（从顶点0开始）：</span><br><span class="line">0 → 1 → 2 → 3 → 4</span><br><span class="line"></span><br><span class="line">可视化：</span><br><span class="line">    0(开始)</span><br><span class="line">    ↓</span><br><span class="line">    1</span><br><span class="line">    ↓</span><br><span class="line">    2</span><br><span class="line">    ↓</span><br><span class="line">    3</span><br><span class="line">    ↓</span><br><span class="line">    4</span><br></pre></td></tr></table></figure><hr><h2 id="🌊-广度优先搜索-BFS">🌊 广度优先搜索(BFS)</h2><h3 id="🎯-核心思想-2">🎯 核心思想</h3><p><strong>广度优先搜索</strong>就像水波纹一样，从起点开始，一层一层地向外扩展，先访问所有相邻的顶点，再访问相邻顶点的相邻顶点。</p><h3 id="🎨-算法流程-2">🎨 算法流程</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">🚀 BFS遍历流程：</span><br><span class="line">1. 从起始顶点开始，加入队列</span><br><span class="line">2. 当队列不为空时：</span><br><span class="line">   a. 取出队首顶点</span><br><span class="line">   b. 标记为已访问</span><br><span class="line">   c. 将所有未访问的邻接顶点加入队列</span><br><span class="line">3. 重复步骤2直到队列为空</span><br></pre></td></tr></table></figure><h3 id="💻-Java实现-2">💻 Java实现</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🌊 广度优先搜索 - 队列实现</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> graph 图的邻接表表示</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> start 起始顶点</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">bfs</span><span class="params">(LinkedList&lt;Integer&gt;[] graph, <span class="type">int</span> start)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">V</span> <span class="operator">=</span> graph.length;</span><br><span class="line">    <span class="type">boolean</span>[] visited = <span class="keyword">new</span> <span class="title class_">boolean</span>[V];</span><br><span class="line">    Queue&lt;Integer&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 🚀 1. 起始顶点入队</span></span><br><span class="line">    queue.offer(start);</span><br><span class="line">    visited[start] = <span class="literal">true</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 🔄 2. 当队列不为空时继续遍历</span></span><br><span class="line">    <span class="keyword">while</span> (!queue.isEmpty()) &#123;</span><br><span class="line">        <span class="comment">// 📋 2.1 取出队首元素</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">v</span> <span class="operator">=</span> queue.poll();</span><br><span class="line">        System.out.print(v + <span class="string">&quot; &quot;</span>);  <span class="comment">// 📝 处理当前顶点</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔄 2.2 将所有未访问的邻接顶点入队</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> neighbor : graph[v]) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!visited[neighbor]) &#123;</span><br><span class="line">                visited[neighbor] = <span class="literal">true</span>;  <span class="comment">// ✅ 标记为已访问</span></span><br><span class="line">                queue.offer(neighbor);     <span class="comment">// 📝 入队等待处理</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="🎨-BFS层级遍历（记录层级信息）">🎨 BFS层级遍历（记录层级信息）</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🌊 广度优先搜索 - 层级遍历（记录每个顶点的层级）</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> graph 图的邻接表表示</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> start 起始顶点</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 每个顶点的层级（距离）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span>[] bfsWithLevel(LinkedList&lt;Integer&gt;[] graph, <span class="type">int</span> start) &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">V</span> <span class="operator">=</span> graph.length;</span><br><span class="line">    <span class="type">int</span>[] level = <span class="keyword">new</span> <span class="title class_">int</span>[V];      <span class="comment">// 🎯 记录每个顶点的层级</span></span><br><span class="line">    Arrays.fill(level, -<span class="number">1</span>);        <span class="comment">// 📝 初始化为-1，表示未访问</span></span><br><span class="line">    Queue&lt;Integer&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 🚀 起始顶点</span></span><br><span class="line">    queue.offer(start);</span><br><span class="line">    level[start] = <span class="number">0</span>;  <span class="comment">// 🎯 起始顶点层级为0</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 🔄 BFS遍历</span></span><br><span class="line">    <span class="keyword">while</span> (!queue.isEmpty()) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">v</span> <span class="operator">=</span> queue.poll();</span><br><span class="line">        <span class="type">int</span> <span class="variable">currentLevel</span> <span class="operator">=</span> level[v];</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔄 处理所有邻接顶点</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> neighbor : graph[v]) &#123;</span><br><span class="line">            <span class="keyword">if</span> (level[neighbor] == -<span class="number">1</span>) &#123;  <span class="comment">// ❌ 未访问的顶点</span></span><br><span class="line">                level[neighbor] = currentLevel + <span class="number">1</span>;  <span class="comment">// 🎯 层级+1</span></span><br><span class="line">                queue.offer(neighbor);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> level;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.LinkedList;</span><br><span class="line"><span class="keyword">import</span> java.util.Queue;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🌊 广度优先搜索实现（二维网格）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BFS</span> &#123;</span><br><span class="line">    <span class="comment">// 定义四个方向：右、下、左、上</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span>[][] dir = &#123;&#123;<span class="number">0</span>, <span class="number">1</span>&#125;, &#123;<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">0</span>, -<span class="number">1</span>&#125;, &#123;-<span class="number">1</span>, <span class="number">0</span>&#125;&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🌊 广度优先搜索主函数（二维网格）</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> grid 二维网格数组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> visited 访问标记数组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> x 起始x坐标</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> y 起始y坐标</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">bfs</span><span class="params">(<span class="type">char</span>[][] grid, <span class="type">boolean</span>[][] visited, <span class="type">int</span> x, <span class="type">int</span> y)</span> &#123;</span><br><span class="line">        <span class="comment">// 定义队列，存储待访问的坐标</span></span><br><span class="line">        Queue&lt;<span class="type">int</span>[]&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">        queue.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;x, y&#125;); <span class="comment">// 起始节点加入队列</span></span><br><span class="line">        visited[x][y] = <span class="literal">true</span>; <span class="comment">// 标记为已访问</span></span><br><span class="line">        <span class="comment">// 🔍 重要特性：在网格/矩阵的BFS实现中，当第一次访问某个节点时：</span></span><br><span class="line">        <span class="comment">//    1️⃣ 该访问路径必定是最短路径</span></span><br><span class="line">        <span class="comment">//    2️⃣ 因为BFS是按距离从近到远逐层扩展的</span></span><br><span class="line">        <span class="comment">//    3️⃣ 后续如果有其他路径到达该节点，距离一定不会更短（因为已经按层级遍历）</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (!queue.isEmpty()) &#123;</span><br><span class="line">            <span class="type">int</span>[] cur = queue.poll(); <span class="comment">// 取出当前节点</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">curX</span> <span class="operator">=</span> cur[<span class="number">0</span>];</span><br><span class="line">            <span class="type">int</span> <span class="variable">curY</span> <span class="operator">=</span> cur[<span class="number">1</span>];</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 遍历四个方向</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">4</span>; i++) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">nextX</span> <span class="operator">=</span> curX + dir[i][<span class="number">0</span>];</span><br><span class="line">                <span class="type">int</span> <span class="variable">nextY</span> <span class="operator">=</span> curY + dir[i][<span class="number">1</span>];</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 检查坐标是否越界</span></span><br><span class="line">                <span class="keyword">if</span> (nextX &lt; <span class="number">0</span> || nextX &gt;= grid.length || nextY &lt; <span class="number">0</span> || nextY &gt;= grid[<span class="number">0</span>].length) &#123;</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 如果节点未被访问过</span></span><br><span class="line">                <span class="keyword">if</span> (!visited[nextX][nextY]) &#123;</span><br><span class="line">                    queue.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;nextX, nextY&#125;); <span class="comment">// 加入队列</span></span><br><span class="line">                    visited[nextX][nextY] = <span class="literal">true</span>; <span class="comment">// 标记为已访问</span></span><br><span class="line">                    <span class="comment">// 🔍 这里第一次访问该节点，路径一定是最短的</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="🎨-BFS遍历示例">🎨 BFS遍历示例</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">图结构：0-1-2</span><br><span class="line">         |</span><br><span class="line">         3-4</span><br><span class="line"></span><br><span class="line">BFS遍历过程（从顶点0开始）：</span><br><span class="line">层级0: 0</span><br><span class="line">层级1: 1 3  </span><br><span class="line">层级2: 2 4</span><br><span class="line"></span><br><span class="line">可视化：</span><br><span class="line">    0(层级0)</span><br><span class="line">    ↓ ↓</span><br><span class="line">    1 3(层级1)</span><br><span class="line">    ↓   ↓</span><br><span class="line">    2   4(层级2)</span><br></pre></td></tr></table></figure><hr><h2 id="🔍-DFS-vs-BFS对比分析">🔍 DFS vs BFS对比分析</h2><h3 id="📊-核心区别对比表">📊 核心区别对比表</h3><table><thead><tr><th>特性</th><th>DFS深度优先</th><th>BFS广度优先</th></tr></thead><tbody><tr><td><strong>数据结构</strong></td><td>栈(Stack)</td><td>队列(Queue)</td></tr><tr><td><strong>遍历顺序</strong></td><td>先深入到底，再回退</td><td>一层一层向外扩展</td></tr><tr><td><strong>空间复杂度</strong></td><td>O(h)，h是树的高度</td><td>O(w)，w是树的宽度</td></tr><tr><td><strong>时间复杂度</strong></td><td>O(V+E)</td><td>O(V+E)</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><h3 id="🎯-选择策略">🎯 选择策略</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">🤔 什么时候用DFS？</span><br><span class="line">✅ 需要找到任意路径</span><br><span class="line">✅ 图的连通性问题</span><br><span class="line">✅ 拓扑排序</span><br><span class="line">✅ 空间受限的情况</span><br><span class="line"></span><br><span class="line">🤔 什么时候用BFS？</span><br><span class="line">✅ 需要找到最短路径</span><br><span class="line">✅ 层级遍历</span><br><span class="line">✅ 单词接龙类问题</span><br><span class="line">✅ 社交网络中的最短距离</span><br></pre></td></tr></table></figure><hr><h2 id="🏗️-实战应用案例">🏗️ 实战应用案例</h2><h3 id="🎯-案例1：岛屿数量（LeetCode-200）">🎯 案例1：岛屿数量（LeetCode 200）</h3><p><strong>问题描述</strong>：给定一个由 ‘1’（陆地）和 ‘0’（水）组成的二维网格，计算岛屿的数量。</p><p><strong>DFS解决方案</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🏝️ 岛屿数量解决方案（LeetCode 200）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NumberOfIslands</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🏝️ 计算岛屿数量（DFS解法）</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> grid 二维网格</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 岛屿数量</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">numIslands</span><span class="params">(<span class="type">char</span>[][] grid)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (grid == <span class="literal">null</span> || grid.length == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> <span class="variable">rows</span> <span class="operator">=</span> grid.length;</span><br><span class="line">        <span class="type">int</span> <span class="variable">cols</span> <span class="operator">=</span> grid[<span class="number">0</span>].length;</span><br><span class="line">        <span class="type">int</span> <span class="variable">islandCount</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔍 遍历整个网格</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; rows; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; cols; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (grid[i][j] == <span class="string">&#x27;1&#x27;</span>) &#123;  <span class="comment">// 🎯 发现新岛屿</span></span><br><span class="line">                    dfs(grid, i, j);      <span class="comment">// 🌊 DFS淹没整个岛屿</span></span><br><span class="line">                    islandCount++;        <span class="comment">// 📊 岛屿计数+1</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> islandCount;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🌊 DFS淹没岛屿（将相连的陆地变成水）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">dfs</span><span class="params">(<span class="type">char</span>[][] grid, <span class="type">int</span> i, <span class="type">int</span> j)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">rows</span> <span class="operator">=</span> grid.length;</span><br><span class="line">        <span class="type">int</span> <span class="variable">cols</span> <span class="operator">=</span> grid[<span class="number">0</span>].length;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🛑 边界检查和终止条件</span></span><br><span class="line">        <span class="keyword">if</span> (i &lt; <span class="number">0</span> || i &gt;= rows || j &lt; <span class="number">0</span> || j &gt;= cols || grid[i][j] == <span class="string">&#x27;0&#x27;</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ✅ 标记当前位置为已访问（淹没）</span></span><br><span class="line">        grid[i][j] = <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔄 向四个方向扩散（上下左右）</span></span><br><span class="line">        dfs(grid, i + <span class="number">1</span>, j);  <span class="comment">// ↓</span></span><br><span class="line">        dfs(grid, i - <span class="number">1</span>, j);  <span class="comment">// ↑</span></span><br><span class="line">        dfs(grid, i, j + <span class="number">1</span>);  <span class="comment">// →</span></span><br><span class="line">        dfs(grid, i, j - <span class="number">1</span>);  <span class="comment">// ←</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">**BFS解决方案**：</span><br><span class="line">```java</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🏝️ 岛屿数量解决方案（LeetCode 200）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NumberOfIslandsBFS</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🏝️ 计算岛屿数量（BFS解法）</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> grid 二维网格</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 岛屿数量</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">numIslands</span><span class="params">(<span class="type">char</span>[][] grid)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (grid == <span class="literal">null</span> || grid.length == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> <span class="variable">rows</span> <span class="operator">=</span> grid.length;</span><br><span class="line">        <span class="type">int</span> <span class="variable">cols</span> <span class="operator">=</span> grid[<span class="number">0</span>].length;</span><br><span class="line">        <span class="type">int</span> <span class="variable">islandCount</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔍 遍历整个网格</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; rows; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; cols; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (grid[i][j] == <span class="string">&#x27;1&#x27;</span>) &#123;  <span class="comment">// 🎯 发现新岛屿</span></span><br><span class="line">                    bfs(grid, i, j);      <span class="comment">// 🌊 BFS淹没整个岛屿</span></span><br><span class="line">                    islandCount++;        <span class="comment">// 📊 岛屿计数+1</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> islandCount;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🌊 BFS淹没岛屿（将相连的陆地变成水）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">bfs</span><span class="params">(<span class="type">char</span>[][] grid, <span class="type">int</span> i, <span class="type">int</span> j)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">rows</span> <span class="operator">=</span> grid.length;</span><br><span class="line">        <span class="type">int</span> <span class="variable">cols</span> <span class="operator">=</span> grid[<span class="number">0</span>].length;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📦 使用队列实现BFS</span></span><br><span class="line">        Queue&lt;<span class="type">int</span>[]&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">        queue.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;i, j&#125;);</span><br><span class="line">        grid[i][j] = <span class="string">&#x27;0&#x27;</span>;  <span class="comment">// ✅ 标记当前位置为已访问（淹没）</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔄 四个方向的偏移量（上下左右）</span></span><br><span class="line">        <span class="type">int</span>[][] directions = &#123;&#123;-<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">0</span>, -<span class="number">1</span>&#125;, &#123;<span class="number">0</span>, <span class="number">1</span>&#125;&#125;;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (!queue.isEmpty()) &#123;</span><br><span class="line">            <span class="type">int</span>[] curr = queue.poll();</span><br><span class="line">            <span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> curr[<span class="number">0</span>];</span><br><span class="line">            <span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> curr[<span class="number">1</span>];</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 🔍 探索四个方向的相邻格子</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span>[] dir : directions) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">nx</span> <span class="operator">=</span> x + dir[<span class="number">0</span>];</span><br><span class="line">                <span class="type">int</span> <span class="variable">ny</span> <span class="operator">=</span> y + dir[<span class="number">1</span>];</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 🛑 边界检查和有效陆地检查</span></span><br><span class="line">                <span class="keyword">if</span> (nx &gt;= <span class="number">0</span> &amp;&amp; nx &lt; rows &amp;&amp; ny &gt;= <span class="number">0</span> &amp;&amp; ny &lt; cols &amp;&amp; grid[nx][ny] == <span class="string">&#x27;1&#x27;</span>) &#123;</span><br><span class="line">                    queue.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;nx, ny&#125;);  <span class="comment">// 📥 将新陆地加入队列</span></span><br><span class="line">                    grid[nx][ny] = <span class="string">&#x27;0&#x27;</span>;              <span class="comment">// ✅ 标记为已访问</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="🎯-案例2：单词接龙（LeetCode-127）">🎯 案例2：单词接龙（LeetCode 127）</h3><p><strong>问题描述</strong>：给定两个单词（beginWord和endWord）和一个字典，找到从beginWord到endWord的最短转换序列的长度。</p><p><strong>BFS解决方案</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"><span class="keyword">import</span> java.util.HashSet;</span><br><span class="line"><span class="keyword">import</span> java.util.LinkedList;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.Queue;</span><br><span class="line"><span class="keyword">import</span> java.util.Set;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 📝 单词接龙解决方案（LeetCode 127）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WordLadder</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 📝 单词接龙 - BFS解法（最短路径）</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> beginWord 起始单词</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> endWord 目标单词</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> wordList 单词字典</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 最短转换序列长度</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">ladderLength</span><span class="params">(String beginWord, String endWord, List&lt;String&gt; wordList)</span> &#123;</span><br><span class="line">        Set&lt;String&gt; wordSet = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;(wordList);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ❌ 如果目标单词不在字典中，无法到达</span></span><br><span class="line">        <span class="keyword">if</span> (!wordSet.contains(endWord)) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        Queue&lt;String&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">        Set&lt;String&gt; visited = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🚀 BFS初始化</span></span><br><span class="line">        queue.offer(beginWord);</span><br><span class="line">        visited.add(beginWord);</span><br><span class="line">        <span class="type">int</span> <span class="variable">level</span> <span class="operator">=</span> <span class="number">1</span>;  <span class="comment">// 🎯 记录层级（路径长度）</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (!queue.isEmpty()) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">size</span> <span class="operator">=</span> queue.size();</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 🎯 处理当前层级的所有单词</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; size; i++) &#123;</span><br><span class="line">                <span class="type">String</span> <span class="variable">currentWord</span> <span class="operator">=</span> queue.poll();</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 🎯 找到目标，返回当前层级</span></span><br><span class="line">                <span class="keyword">if</span> (currentWord.equals(endWord)) <span class="keyword">return</span> level;</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 🔄 生成所有可能的下一个单词</span></span><br><span class="line">                <span class="keyword">for</span> (String nextWord : getNextWords(currentWord, wordSet, visited)) &#123;</span><br><span class="line">                    visited.add(nextWord);</span><br><span class="line">                    queue.offer(nextWord);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            level++;  <span class="comment">// 🎯 进入下一层级</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;  <span class="comment">// ❌ 无法到达目标</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 📝 生成所有可能的下一个单词（改变一个字母）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;String&gt; <span class="title function_">getNextWords</span><span class="params">(String word, Set&lt;String&gt; wordSet, Set&lt;String&gt; visited)</span> &#123;</span><br><span class="line">        List&lt;String&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="type">char</span>[] chars = word.toCharArray();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔄 尝试改变每个位置的字母</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; chars.length; i++) &#123;</span><br><span class="line">            <span class="type">char</span> <span class="variable">original</span> <span class="operator">=</span> chars[i];</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 🔄 尝试所有可能的字母</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">char</span> <span class="variable">c</span> <span class="operator">=</span> <span class="string">&#x27;a&#x27;</span>; c &lt;= <span class="string">&#x27;z&#x27;</span>; c++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (c == original) <span class="keyword">continue</span>;  <span class="comment">// ❌ 跳过原字母</span></span><br><span class="line">                </span><br><span class="line">                chars[i] = c;</span><br><span class="line">                <span class="type">String</span> <span class="variable">newWord</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(chars);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// ✅ 如果新单词在字典中且未访问过，加入结果</span></span><br><span class="line">                <span class="keyword">if</span> (wordSet.contains(newWord) &amp;&amp; !visited.contains(newWord)) &#123;</span><br><span class="line">                    result.add(newWord);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            chars[i] = original;  <span class="comment">// ↩️ 恢复原字母</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="⚡-性能分析与优化">⚡ 性能分析与优化</h2><h3 id="📊-时间复杂度对比">📊 时间复杂度对比</h3><table><thead><tr><th>算法</th><th>时间复杂度</th><th>空间复杂度</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>DFS</strong></td><td>O(V + E)</td><td>O(V)</td><td>连通性、路径搜索</td></tr><tr><td><strong>BFS</strong></td><td>O(V + E)</td><td>O(V)</td><td>最短路径、层级遍历</td></tr></tbody></table><blockquote><p>💡 <strong>说明</strong>：V是顶点数，E是边数</p></blockquote><h3 id="🚀-优化技巧">🚀 优化技巧</h3><h4 id="DFS优化">DFS优化</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.HashMap;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 DFS优化技巧</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DFSOptimization</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 简单的树节点定义</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Node</span> &#123;</span><br><span class="line">        <span class="type">int</span> value;</span><br><span class="line">        List&lt;Node&gt; children;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">Node</span><span class="params">(<span class="type">int</span> value)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.value = value;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1️⃣ 剪枝优化 - 提前终止</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">dfsWithPruning</span><span class="params">(Node node, <span class="type">int</span> target, <span class="type">int</span> currentSum)</span> &#123;</span><br><span class="line">        <span class="comment">// 🛑 剪枝：当前和已经超过目标</span></span><br><span class="line">        <span class="keyword">if</span> (currentSum &gt; target) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🎯 找到解，提前终止</span></span><br><span class="line">        <span class="keyword">if</span> (currentSum == target) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔄 继续搜索</span></span><br><span class="line">        <span class="keyword">for</span> (Node child : node.children) &#123;</span><br><span class="line">            <span class="keyword">if</span> (dfsWithPruning(child, target, currentSum + child.value)) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;  <span class="comment">// 🎯 提前终止</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2️⃣ 记忆化搜索 - 避免重复计算</span></span><br><span class="line">    <span class="keyword">private</span> Map&lt;String, Boolean&gt; memo = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">dfsWithMemo</span><span class="params">(String state)</span> &#123;</span><br><span class="line">        <span class="comment">// ✅ 检查是否已经计算过</span></span><br><span class="line">        <span class="keyword">if</span> (memo.containsKey(state)) &#123;</span><br><span class="line">            <span class="keyword">return</span> memo.get(state);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🎯 基础情况（示例实现）</span></span><br><span class="line">        <span class="keyword">if</span> (isBaseCase(state)) &#123;</span><br><span class="line">            memo.put(state, <span class="literal">true</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔄 递归搜索</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">result</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">for</span> (String nextState : getNextStates(state)) &#123;</span><br><span class="line">            <span class="keyword">if</span> (dfsWithMemo(nextState)) &#123;</span><br><span class="line">                result = <span class="literal">true</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 📝 记录结果</span></span><br><span class="line">        memo.put(state, result);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 判断是否为基础情况（示例实现）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">isBaseCase</span><span class="params">(String state)</span> &#123;</span><br><span class="line">        <span class="comment">// 示例逻辑：空状态或特定终止状态</span></span><br><span class="line">        <span class="keyword">return</span> state.isEmpty() || <span class="string">&quot;goal&quot;</span>.equals(state);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取下一个可能的状态（示例实现）</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;String&gt; <span class="title function_">getNextStates</span><span class="params">(String state)</span> &#123;</span><br><span class="line">        <span class="comment">// 示例逻辑：这里应该根据具体问题返回可能的下一个状态</span></span><br><span class="line">        <span class="comment">// 实际使用时需要根据具体业务逻辑实现</span></span><br><span class="line">        <span class="keyword">return</span> List.of();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="BFS优化">BFS优化</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.HashSet;</span><br><span class="line"><span class="keyword">import</span> java.util.LinkedList;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.Set;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🎯 BFS优化技巧</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BFSOptimization</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1️⃣ 双向BFS - 从起点和终点同时开始</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">bidirectionalBFS</span><span class="params">(String beginWord, String endWord, Set&lt;String&gt; wordSet)</span> &#123;</span><br><span class="line">        Set&lt;String&gt; beginSet = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">        Set&lt;String&gt; endSet = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">        Set&lt;String&gt; visited = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;(); <span class="comment">// 记录已访问的单词</span></span><br><span class="line">        </span><br><span class="line">        beginSet.add(beginWord);</span><br><span class="line">        endSet.add(endWord);</span><br><span class="line">        visited.add(beginWord);</span><br><span class="line">        visited.add(endWord);</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> <span class="variable">level</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (!beginSet.isEmpty() &amp;&amp; !endSet.isEmpty()) &#123;</span><br><span class="line">            <span class="comment">// 🎯 总是从较小的集合开始扩展</span></span><br><span class="line">            <span class="keyword">if</span> (beginSet.size() &gt; endSet.size()) &#123;</span><br><span class="line">                Set&lt;String&gt; temp = beginSet;</span><br><span class="line">                beginSet = endSet;</span><br><span class="line">                endSet = temp;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            Set&lt;String&gt; nextLevel = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 🔄 扩展当前层级</span></span><br><span class="line">            <span class="keyword">for</span> (String word : beginSet) &#123;</span><br><span class="line">                <span class="keyword">for</span> (String nextWord : getNextWords(word, wordSet)) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (endSet.contains(nextWord)) &#123;</span><br><span class="line">                        <span class="keyword">return</span> level + <span class="number">1</span>;  <span class="comment">// 🎯 双向相遇</span></span><br><span class="line">                    &#125;</span><br><span class="line">                    </span><br><span class="line">                    <span class="keyword">if</span> (!visited.contains(nextWord)) &#123;</span><br><span class="line">                        visited.add(nextWord);</span><br><span class="line">                        nextLevel.add(nextWord);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            beginSet = nextLevel;</span><br><span class="line">            level++;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 📝 生成所有可能的下一个单词（改变一个字母）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;String&gt; <span class="title function_">getNextWords</span><span class="params">(String word, Set&lt;String&gt; wordSet)</span> &#123;</span><br><span class="line">        List&lt;String&gt; result = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">        <span class="type">char</span>[] chars = word.toCharArray();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 🔄 尝试改变每个位置的字母</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; chars.length; i++) &#123;</span><br><span class="line">            <span class="type">char</span> <span class="variable">original</span> <span class="operator">=</span> chars[i];</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 🔄 尝试所有可能的字母</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">char</span> <span class="variable">c</span> <span class="operator">=</span> <span class="string">&#x27;a&#x27;</span>; c &lt;= <span class="string">&#x27;z&#x27;</span>; c++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (c == original) <span class="keyword">continue</span>;  <span class="comment">// ❌ 跳过原字母</span></span><br><span class="line">                </span><br><span class="line">                chars[i] = c;</span><br><span class="line">                <span class="type">String</span> <span class="variable">newWord</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(chars);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// ✅ 如果新单词在字典中，加入结果</span></span><br><span class="line">                <span class="keyword">if</span> (wordSet.contains(newWord)) &#123;</span><br><span class="line">                    result.add(newWord);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            chars[i] = original;  <span class="comment">// ↩️ 恢复原字母</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="🎯-经典LeetCode题目">🎯 经典LeetCode题目</h2><h3 id="📝-基础题目（⭐⭐☆☆☆）">📝 基础题目（⭐⭐☆☆☆）</h3><table><thead><tr><th>题号</th><th>题目名称</th><th>算法类型</th><th>难度</th></tr></thead><tbody><tr><td>200</td><td>岛屿数量</td><td>DFS</td><td>⭐⭐☆☆☆</td></tr><tr><td>102</td><td>二叉树的层序遍历</td><td>BFS</td><td>⭐⭐☆☆☆</td></tr><tr><td>104</td><td>二叉树的最大深度</td><td>DFS/BFS</td><td>⭐⭐☆☆☆</td></tr><tr><td>111</td><td>二叉树的最小深度</td><td>BFS</td><td>⭐⭐☆☆☆</td></tr></tbody></table><h3 id="📝-进阶题目（⭐⭐⭐☆☆）">📝 进阶题目（⭐⭐⭐☆☆）</h3><table><thead><tr><th>题号</th><th>题目名称</th><th>算法类型</th><th>难度</th></tr></thead><tbody><tr><td>127</td><td>单词接龙</td><td>BFS</td><td>⭐⭐⭐☆☆</td></tr><tr><td>130</td><td>被围绕的区域</td><td>DFS</td><td>⭐⭐⭐☆☆</td></tr><tr><td>417</td><td>太平洋大西洋水流问题</td><td>DFS/BFS</td><td>⭐⭐⭐☆☆</td></tr><tr><td>207</td><td>课程表</td><td>DFS/拓扑排序</td><td>⭐⭐⭐☆☆</td></tr></tbody></table><h3 id="📝-高难度题目（⭐⭐⭐⭐☆）">📝 高难度题目（⭐⭐⭐⭐☆）</h3><table><thead><tr><th>题号</th><th>题目名称</th><th>算法类型</th><th>难度</th></tr></thead><tbody><tr><td>126</td><td>单词接龙II</td><td>BFS + DFS</td><td>⭐⭐⭐⭐☆</td></tr><tr><td>269</td><td>火星词典</td><td>拓扑排序</td><td>⭐⭐⭐⭐☆</td></tr><tr><td>329</td><td>矩阵中的最长递增路径</td><td>DFS + 记忆化</td><td>⭐⭐⭐⭐☆</td></tr><tr><td>444</td><td>序列重建</td><td>拓扑排序</td><td>⭐⭐⭐⭐☆</td></tr></tbody></table><hr><h2 id="🔄-多源BFS与单源BFS的对比与应用">🔄 多源BFS与单源BFS的对比与应用</h2><h3 id="📚-概念解析">📚 概念解析</h3><p><strong>单源BFS (Single Source BFS)</strong>：从图中的<strong>单个起点</strong>出发，逐层遍历所有可达节点的搜索算法。</p><p><strong>多源BFS (Multi Source BFS)</strong>：从图中的<strong>多个起点</strong>同时出发，逐层扩展的搜索算法。适用于需要从多个起点同时进行广度优先搜索的场景。</p><h3 id="📊-对比分析">📊 对比分析</h3><table><thead><tr><th>特性</th><th>单源BFS</th><th>多源BFS</th></tr></thead><tbody><tr><td>起点数量</td><td>1个</td><td>多个</td></tr><tr><td>初始化</td><td>队列中只放入一个起始节点</td><td>队列中放入所有起始节点</td></tr><tr><td>适用场景</td><td>最短路径、连通性检测等</td><td>多源最短路径、洪水填充等</td></tr><tr><td>时间复杂度</td><td>O(V+E)</td><td>O(V+E)</td></tr><tr><td>空间复杂度</td><td>O(V)</td><td>O(V)</td></tr></tbody></table><h3 id="🎯-应用场景">🎯 应用场景</h3><h4 id="单源BFS的典型应用">单源BFS的典型应用</h4><ul><li>最短路径问题（无权图）</li><li>层序遍历（树结构）</li><li>连通性检测</li><li>拓扑排序（结合入度）</li></ul><h4 id="多源BFS的典型应用">多源BFS的典型应用</h4><ul><li>疫情传播模型</li><li>洪水填充算法</li><li>多源最短距离（如房间中的灯照亮所有区域所需时间）</li><li>矩阵中的01距离（计算每个1到最近0的距离）</li></ul><h3 id="💻-代码示例对比">💻 代码示例对比</h3><h4 id="单源BFS代码框架">单源BFS代码框架</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">singleSourceBFS</span><span class="params">(<span class="type">int</span>[][] grid, <span class="type">int</span> m, <span class="type">int</span>[][] dist, <span class="type">int</span> startX, <span class="type">int</span> startY)</span> &#123;</span><br><span class="line">    <span class="comment">// 将起始节点加入队列，并初始化距离为 0</span></span><br><span class="line">    Queue&lt;<span class="type">int</span>[]&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">    queue.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;startX, startY&#125;);</span><br><span class="line">    dist[startX][startY] = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span>[][] dirs = &#123;&#123;<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">0</span>, <span class="number">1</span>&#125;, &#123;-<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">0</span>, -<span class="number">1</span>&#125;&#125;;</span><br><span class="line">    <span class="keyword">while</span> (!queue.isEmpty()) &#123;</span><br><span class="line">        <span class="type">int</span>[] cur = queue.poll();</span><br><span class="line">        <span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> cur[<span class="number">0</span>], y = cur[<span class="number">1</span>];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span>[] dir : dirs) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">nx</span> <span class="operator">=</span> x + dir[<span class="number">0</span>], ny = y + dir[<span class="number">1</span>];</span><br><span class="line">            <span class="keyword">if</span> (nx &gt;= <span class="number">0</span> &amp;&amp; nx &lt; m &amp;&amp; ny &gt;= <span class="number">0</span> &amp;&amp; ny &lt; m) &#123;</span><br><span class="line">                <span class="keyword">if</span> (grid[nx][ny] != <span class="number">2</span> &amp;&amp; dist[nx][ny] == -<span class="number">1</span>) &#123;</span><br><span class="line">                    dist[nx][ny] = dist[x][y] + <span class="number">1</span>;</span><br><span class="line">                    queue.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;nx, ny&#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="多源BFS代码框架">多源BFS代码框架</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">multiSourceBFS</span><span class="params">(<span class="type">int</span>[][] grid, <span class="type">int</span> m, <span class="type">int</span>[][] dist)</span> &#123;</span><br><span class="line">    Queue&lt;<span class="type">int</span>[]&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 将所有起点（3）加入队列，并初始化距离为 0</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; m; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (grid[i][j] == <span class="number">3</span>) &#123;</span><br><span class="line">                dist[i][j] = <span class="number">0</span>;</span><br><span class="line">                queue.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;i, j&#125;);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span>[][] dirs = &#123;&#123;<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">0</span>, <span class="number">1</span>&#125;, &#123;-<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">0</span>, -<span class="number">1</span>&#125;&#125;;</span><br><span class="line">    <span class="keyword">while</span> (!queue.isEmpty()) &#123;</span><br><span class="line">        <span class="type">int</span>[] cur = queue.poll();</span><br><span class="line">        <span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> cur[<span class="number">0</span>], y = cur[<span class="number">1</span>];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span>[] dir : dirs) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">nx</span> <span class="operator">=</span> x + dir[<span class="number">0</span>], ny = y + dir[<span class="number">1</span>];</span><br><span class="line">            <span class="keyword">if</span> (nx &gt;= <span class="number">0</span> &amp;&amp; nx &lt; m &amp;&amp; ny &gt;= <span class="number">0</span> &amp;&amp; ny &lt; m) &#123;</span><br><span class="line">                <span class="keyword">if</span> (grid[nx][ny] != <span class="number">2</span> &amp;&amp; dist[nx][ny] == -<span class="number">1</span>) &#123;</span><br><span class="line">                    <span class="comment">// 计算新距离</span></span><br><span class="line">                    dist[nx][ny] = dist[x][y] + <span class="number">1</span>;</span><br><span class="line">                    queue.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;nx, ny&#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="🚀-实战案例：矩阵中的01距离（LeetCode-542）">🚀 实战案例：矩阵中的01距离（LeetCode 542）</h3><p><strong>问题描述</strong>：给定一个由0和1组成的矩阵，找出每个1到最近的0的距离。</p><p><strong>多源BFS解决方案</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 🏙️ 矩阵中的01距离（LeetCode 542）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Matrix01</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 🏙️ 计算每个1到最近0的距离（多源BFS解法）</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> mat 输入矩阵</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 距离矩阵</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span>[][] updateMatrix(<span class="type">int</span>[][] mat) &#123;</span><br><span class="line">        <span class="keyword">if</span> (mat == <span class="literal">null</span> || mat.length == <span class="number">0</span>) <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">0</span>][<span class="number">0</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> <span class="variable">m</span> <span class="operator">=</span> mat.length;</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> mat[<span class="number">0</span>].length;</span><br><span class="line">        <span class="type">int</span>[][] dist = <span class="keyword">new</span> <span class="title class_">int</span>[m][n]; <span class="comment">// 存储距离结果</span></span><br><span class="line">        Queue&lt;<span class="type">int</span>[]&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 初始化：将所有0的位置加入队列（多源起点）</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; m; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (mat[i][j] == <span class="number">0</span>) &#123;</span><br><span class="line">                    queue.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;i, j&#125;);</span><br><span class="line">                    dist[i][j] = <span class="number">0</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    dist[i][j] = -<span class="number">1</span>; <span class="comment">// 标记未访问</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 四个方向的偏移量</span></span><br><span class="line">        <span class="type">int</span>[][] dirs = &#123;&#123;-<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">0</span>, -<span class="number">1</span>&#125;, &#123;<span class="number">0</span>, <span class="number">1</span>&#125;&#125;;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 多源BFS遍历</span></span><br><span class="line">        <span class="keyword">while</span> (!queue.isEmpty()) &#123;</span><br><span class="line">            <span class="type">int</span>[] curr = queue.poll();</span><br><span class="line">            <span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> curr[<span class="number">0</span>];</span><br><span class="line">            <span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> curr[<span class="number">1</span>];</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span>[] dir : dirs) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">nx</span> <span class="operator">=</span> x + dir[<span class="number">0</span>];</span><br><span class="line">                <span class="type">int</span> <span class="variable">ny</span> <span class="operator">=</span> y + dir[<span class="number">1</span>];</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 边界检查和未访问检查</span></span><br><span class="line">                <span class="keyword">if</span> (nx &gt;= <span class="number">0</span> &amp;&amp; nx &lt; m &amp;&amp; ny &gt;= <span class="number">0</span> &amp;&amp; ny &lt; n &amp;&amp; dist[nx][ny] == -<span class="number">1</span>) &#123;</span><br><span class="line">                    dist[nx][ny] = dist[x][y] + <span class="number">1</span>; <span class="comment">// 距离等于父节点距离+1</span></span><br><span class="line">                    queue.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;nx, ny&#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> dist;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="🧠-记忆口诀与技巧">🧠 记忆口诀与技巧</h2><h3 id="🎯-DFS记忆口诀">🎯 DFS记忆口诀</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">🌊 DFS三步走：</span><br><span class="line">1️⃣ 标记访问 ✅</span><br><span class="line">2️⃣ 递归邻居 🔄</span><br><span class="line">3️⃣ 回溯撤销 ↩️</span><br><span class="line"></span><br><span class="line">💡 DFS精髓：</span><br><span class="line">&quot;一条路走到黑，不撞南墙不回头&quot;</span><br></pre></td></tr></table></figure><h3 id="🎯-BFS记忆口诀">🎯 BFS记忆口诀</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">🌊 BFS四步曲：</span><br><span class="line">1️⃣ 队列初始化 📋</span><br><span class="line">2️⃣ 取出队首元素 📤</span><br><span class="line">3️⃣ 处理并标记 ✅</span><br><span class="line">4️⃣ 邻接顶点入队 📥</span><br><span class="line"></span><br><span class="line">💡 BFS精髓：</span><br><span class="line">&quot;层层推进，稳扎稳打，保证最短&quot;</span><br></pre></td></tr></table></figure><h3 id="🎨-选择策略口诀">🎨 选择策略口诀</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">🤔 算法选择口诀：</span><br><span class="line"></span><br><span class="line">📏 要测距离用BFS，层层推进保最短</span><br><span class="line">🗺️ 要探路径用DFS，深入到底再回退</span><br><span class="line">⚡ 空间紧张用DFS，高度有限省空间</span><br><span class="line">🎯 连通性问题两者皆可，看具体需求选算法</span><br></pre></td></tr></table></figure><hr><h2 id="📖-参考资料">📖 参考资料</h2><h3 id="📚-中文资源">📚 中文资源</h3><ul><li><a href="https://leetcode.cn/explore/learn/card/graph/">图论算法基础 - 力扣中国</a> 🇨🇳</li><li><a href="https://zhuanlan.zhihu.com/p/267104042">图的遍历算法详解 - 知乎专栏</a> 🇨🇳</li><li><a href="https://www.cnblogs.com/skywang12345/p/3711483.html">深度优先搜索和广度优先搜索 - 博客园</a> 🇨🇳</li><li><a href="https://www.runoob.com/java/java-graph-algorithms.html">Java图算法实现 - 菜鸟教程</a> 🇨🇳</li><li><a href="https://visualgo.net/zh">图算法可视化工具 - VisuAlgo</a> 🇨🇳</li></ul><h3 id="📚-经典教材">📚 经典教材</h3><ul><li><a href="https://book.douban.com/subject/1885170/">算法导论（第3版）</a> - 第22章 图的基本算法</li><li><a href="https://book.douban.com/subject/19952400/">算法（第4版）</a> - 第4.1节 图的定义</li><li><a href="https://book.douban.com/subject/25805120/">图论及其应用</a> - 图遍历算法专题</li></ul><h3 id="🎬-视频教程">🎬 视频教程</h3><ul><li><a href="https://www.bilibili.com/video/BV1tx411U7xH">MIT算法导论课程 - 图遍历</a> 🇨🇳</li><li><a href="https://www.coursera.org/learn/algorithms-graphs">斯坦福大学图算法课程</a></li></ul><hr><h2 id="🎓-学习总结">🎓 学习总结</h2><h3 id="🎯-核心要点回顾">🎯 核心要点回顾</h3><ol><li><strong>DFS</strong> = 栈 + 递归 + 回溯，适合路径搜索</li><li><strong>BFS</strong> = 队列 + 层级 + 最短，适合距离计算</li><li><strong>时间复杂度</strong>都是O(V+E)，但空间复杂度不同</li><li><strong>选择标准</strong>：看是否需要最短路径和层级信息</li></ol><h3 id="🚀-进阶学习路线">🚀 进阶学习路线</h3><ol><li><strong>📚 基础巩固</strong>：熟练掌握DFS和BFS模板</li><li><strong>⚡ 技巧提升</strong>：学习双向BFS、记忆化搜索等优化</li><li><strong>🏗️ 实战应用</strong>：解决图论相关问题</li><li><strong>🔬 算法扩展</strong>：学习拓扑排序、最短路径算法</li></ol><h3 id="💡-学习建议">💡 学习建议</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">📝 建议1：多画图辅助理解，可视化遍历过程</span><br><span class="line">📝 建议2：熟练掌握两种算法的模板代码</span><br><span class="line">📝 建议3：通过大量练习培养算法直觉</span><br><span class="line">📝 建议4：理解适用场景，学会算法选择</span><br><span class="line">📝 建议5：关注优化技巧，提升算法效率</span><br></pre></td></tr></table></figure><hr><p><em>最后更新：2025-01-15</em><br><em>作者：算法学习小分队</em></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;🌐 图的深度优先搜索(DFS)与广度优先搜索(BFS)算法详解&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;学习目标&lt;/strong&gt;：掌握图的两种基本遍历算法，理解其原理、区别与实战应用&lt;/p&gt;
&lt;p&gt;⭐ &lt;strong&gt;难度等级&lt;/strong&gt;</summary>
      
    
    
    
    <category term="书山指路" scheme="https://blog.adoreorg.cn/categories/%E4%B9%A6%E5%B1%B1%E6%8C%87%E8%B7%AF/"/>
    
    
    <category term="算法" scheme="https://blog.adoreorg.cn/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="数据结构" scheme="https://blog.adoreorg.cn/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    
    <category term="LeetCode" scheme="https://blog.adoreorg.cn/tags/LeetCode/"/>
    
    <category term="ACM" scheme="https://blog.adoreorg.cn/tags/ACM/"/>
    
    <category term="DFS" scheme="https://blog.adoreorg.cn/tags/DFS/"/>
    
    <category term="深度优先搜索" scheme="https://blog.adoreorg.cn/tags/%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2/"/>
    
    <category term="广度优先搜索" scheme="https://blog.adoreorg.cn/tags/%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2/"/>
    
    <category term="BFS" scheme="https://blog.adoreorg.cn/tags/BFS/"/>
    
    <category term="图算法" scheme="https://blog.adoreorg.cn/tags/%E5%9B%BE%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
</feed>
