<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:webfeeds="http://webfeeds.org/rss/1.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>古时的风筝</title><link>https://www.showcodeba.com/</link><description>Recent content on 古时的风筝</description><webfeeds:icon>https://www.showcodeba.com/images/photo.png</webfeeds:icon><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Mon, 14 Aug 2023 08:56:23 +0800</lastBuildDate><atom:link href="https://www.showcodeba.com/index.xml" rel="self" type="application/rss+xml"/><item><title>终于知道公钥、私钥、对称、非对称加密是什么了</title><link>https://www.showcodeba.com/category/java/%E5%85%AC%E9%92%A5%E7%A7%81%E9%92%A5%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86/</link><pubDate>Mon, 14 Aug 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/java/%E5%85%AC%E9%92%A5%E7%A7%81%E9%92%A5%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86/</guid><description>终于知道公钥、私钥、对称、非对称加密是什么了</description><content:encoded><![CDATA[<p>有接入过第三方服务的同学应该都接触过公钥、私钥这类的说明，尤其是一些对参数要求验证的服务，比如支付类的。</p>
<p>通常对于高保密要求的参数都会有加密要求，这时候，如果你之前不了解加密算法，就很容易被公钥、私钥的使用给绕迷糊了。有时候虽然接口都调通了，但是还是一头雾水，这就通了?</p>
<p>那接下来我们就来解开这团迷雾。咱们只讲逻辑和一部分逻辑，不讲数学算法，因为数学这块儿我也不是很懂。</p>
<p>加密算法在大类上分为对称机密和非对称加密，都用来加密，只不过使用场景、性能、安全性方面有些不同。</p>
<h2 id="首先说什么是加密呢">首先说什么是加密呢?</h2>
<p>加密就是通过一种方式将一个显而易见的东西变成难以理解的东西。</p>
<p>比如这儿有个字符串“我真棒”，这三个字儿要摆在你面前不是一眼就知道我很棒了吗。</p>
<p>但是我要给你看这样一串东西呢？你还能一下子知道是什么意思吗？</p>
<pre tabindex="0"><code>232 10 5,33 50 12,109 45 1
</code></pre><p>其实这还是那三个字，只不过是经过加密的密文，只有知道了加密的方法才能还原出来，也就是解密。</p>
<p>加密的过程可能是这样的：</p>
<ol>
<li>首先买一本叫做《人间清醒》的书；</li>
<li>然后按照逗号分隔，每一个逗号分隔开的表示一个字；</li>
<li>然后每一组是三个数字，第一个数字表示所在页，第二个数字表示所在行，第三个数字表示所在列（第几个字），所以 <code>232 10 5</code>，表示第 232 页，第 10 行，第 5 个字。</li>
</ol>
<p>还有，比如我之前有个温州的同事，他打电话的时候对于我们来说就是语音加密了，能听见他的声音，但是说的是什么内容一个字也听不出来。这也可以理解为一种加密，把普通话的发音转换成温州方言的发音。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230814112751914.png"></p>
<h2 id="对称加密">对称加密</h2>
<p>对称加密中所说的对称是对加密和解密而言的，也就是加密和解密的过程使用相同的密钥。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230814114816786.png"></p>
<p>我们经常用到的落库加密、文件加密都可以使用对称加密的方式。</p>
<p>目前最常用也是最安全的对称加密算法是 AES，AES 还分为 AES-128、AES-192 和 AES256，后面的数字代表加密密钥的位数，位数越高呢，加密效果也就越好，更加不容易被破解。同时，位数越高，加密和解密过程中的计算量也会越大，内存占用也就更大，消耗的资源更多，需要的时间也就更长。</p>
<p>有利有弊，看你的需求而定。基本上，一般场景下 128 位就足够安全了。AES 到目前为止，可以说没有漏洞，而且 128 位就可以保证不会被暴力破解。而更高位数的可能会用到国家级的保密数据上。</p>
<p>AES 是分组加密算法，除此之外，大部分的加密算法都是分组加密算法。</p>
<p>块加密算法就是将需要加密的数据分成一个个的固定长度的分组，比如 128 位一组，然后分别用算法对每一组进行加密，如果最后一组不足 128 位的话，还要用填充算法进行填充，保证达到 128 位。</p>
<p>常用的分组算法有 CTR 和 GCM，CTR 和 GCM 有并行计算的能力，并且, GCM 还能额外提供对消息完整性、真实性的验证能力。</p>
<p>所以我们在某些地方可能看到 AES-128-GCM、AES-256-CTR 这样的写法，前面表示加密算法，后面代表分组算法。</p>
<h3 id="不足之处">不足之处</h3>
<p>对称加密本身从算法层面来说已经足够安全了，但是在密钥分发方面有些不太容易管理。</p>
<p>因为加解密的密钥相同。我加密的数据想要被别人使用，我就要把密钥告诉要使用的人。知道密钥的人、保存密钥的服务器越多，风险就越大。约束自己容易，约束别人难啊。但凡有一方不小心把密钥泄露就完。一个木桶能装多少水是由最低的一块木板决定的。</p>
<h2 id="非对称加密">非对称加密</h2>
<p>由于对称加密的密钥分发问题，非对称加密算法可以完美的解决。</p>
<p>刚毕业不就的时候，和第三方服务做集成，有关于接口参数加密的指引文档，虽然按照人家提供的 demo 可以正常集成，但是文档上说的公钥、私钥还是搞的很迷糊。</p>
<p>现在就来捋一捋啊，就以第三方服务角度来说。假设我是一个支付服务商，为大家提供支付接口的。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230814154311673.png"></p>
<p><strong>公钥</strong></p>
<p>公钥是开放的，谁都可以获取。我作为一个支付服务商，任何到我平台上注册的用户都可以获取到公钥，公钥可以是相同的。</p>
<p><strong>私钥</strong></p>
<p>私钥是绝密的，我作为一个支付服务商，必须将私钥妥善保存，无论是保存在数据库中还是保存在服务器，都必须保证私钥不对外，只有我自己可以使用。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230814133955745.png"></p>
<ol>
<li>使用我服务的用户获取公钥；</li>
<li>调用方（客户）用公钥对接口参数进行加密，然后调用接口；</li>
<li>我方（支付服务商）用私钥对参数进行解密，然后使用参数进行业务处理；</li>
</ol>
<p>以上是公钥、私钥的使用过程，这样一看就很明确了。</p>
<p>之所以说是非对称加密，是因为私钥只是用来解密的，用公钥加密过的数据，只有用对应的私钥才能解密出来。所以说，即使有那么多人得到了相同的公钥，也无法获取别人加密过的数据。</p>
<p>最常用的非对称加密算法是 RSA ，RSA 有 1024、2048、3072、4096、8129、16384 甚至更多位。目前 <strong>3072</strong> 位及以上的密钥长度被认为是安全的，曾经大量使用的 <strong>2048</strong> 位 RSA 现在被破解的风险在不断提升，已经不推荐使用了</p>
<p>RSA 算法的性能要比对称加密 AES 算法差 1000 倍左右，虽然数学原理上不太明白，但是有数学常识就可以知道，使用不同的密钥进行加密和解密，必然要比使用相同密钥的算法复杂很多，由此肯定会带来性能上的开销。</p>
<p>非对称加密适合用于那些对安全性要求更高的场景，例如支付场景、数字证书、数据加密传输等等。</p>
]]></content:encoded></item><item><title>什么时候用 Runnable？什么时候用 Callable ？</title><link>https://www.showcodeba.com/category/java/Runable%E5%92%8CCallable/</link><pubDate>Mon, 07 Aug 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/java/Runable%E5%92%8CCallable/</guid><description>什么时候用 Runnable？什么时候用 Callable ？</description><content:encoded><![CDATA[<p><strong>大家好，我是风筝。</strong></p>
<p>提到 Java 就不得不说多线程了，就算你不想说，面试官也得让你说呀，对不对。那说到多线程，就不得提线程了（这不废话吗）。那说到线程，就不得不说<code>Runnable</code>和<code>Callable</code>这两个家伙了。</p>
<p>说熟悉也是真熟悉，在刚学习多线程的时候，第一个例子大概就是下面这样子的。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">new</span><span class="w"> </span><span class="n">Thread</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">Runnable</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">run</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;执行线程&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">Thread</span><span class="p">.</span><span class="na">currentThread</span><span class="p">().</span><span class="na">getName</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}).</span><span class="na">start</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><p>看到了 <code>Runnable</code>的身影，有时候还会看到<code>Callable</code>的。</p>
<p>但是说很熟悉吧，印象也不是很大，好像就用了一下这两位的名号，然后剩下的部分就跟他俩没啥关系了。</p>
<p>今天，我们就来看看这两位到底是什么，有什么区别，什时候应该用 <code>Runnable</code>，什么时候又应该用 <code>Callable</code>。</p>
<h2 id="runnable">Runnable</h2>
<p>自从 Java 诞生，<code>Runnable</code>就存在了，元老中的元老了，在 1.5 之前，如果你想使用线程，那必须要实现自 <code>Runnable</code>。因为到了 JDK1.5，JDK 才加入了<code>Callable</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@FunctionalInterface</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">interface</span> <span class="nc">Runnable</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">abstract</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">run</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>是不是接口非常简单，就一个抽象方法。</p>
<p>其实还可以再简化一下， <code>@FunctionalInterface</code>标明这个接口是一个函数式接口。函数式接口是在 JDK8 才加入的，为的就是实现函数式编程，就那种 Lambada 表达式。</p>
<p>所以在 JDK1.7 中，是没有<code>@FunctionalInterface</code>修饰的，简简单单。我们找到 JDK1.7 的 <code>Ruunable</code>实现，是下面这样子</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">interface</span> <span class="nc">Runnable</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">abstract</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">run</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>想了解更多函数式编程的话，可以看这篇文章：<a href="https://mp.weixin.qq.com/s/WSqLJEvO42uJ-DbOC2D36w">Lambda、函数式接口、Stream 一次性全给你</a></p>
<p>如果一个线程类要实现 <code>Runnable</code> 接口，则这个类必须定义一个名为 <code>run</code> 的无参数方法。</p>
<p>实现了 <code>Runnable</code> 接口的类可以通过实例化一个 <code>Thread</code> 实例，并将自身作为目标传递来运行。</p>
<h4 id="举个例子">举个例子</h4>
<p>首先定义一个<code>RunnableThread</code>类，并实现自（implements）<code>Runnable</code>接口，然后重写 <code>run</code>方法。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">RunnableThread</span><span class="w"> </span><span class="kd">implements</span><span class="w"> </span><span class="n">Runnable</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">run</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;当前线程名称&#34;</span><span class="o">+</span><span class="w"> </span><span class="n">Thread</span><span class="p">.</span><span class="na">currentThread</span><span class="p">().</span><span class="na">getName</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>使用<code>RunnableThread</code>作为线程类（Thread）实例化的参数，然后调用<code>run</code>方法。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">RunnableThread</span><span class="w"> </span><span class="n">runnableThread</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">RunnableThread</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">Thread</span><span class="w"> </span><span class="n">thread</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">Thread</span><span class="p">(</span><span class="n">runnableThread</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">thread</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><p>注意，是调用新 new 出来的 Thread 实例的<code>start()</code> 方法，不要调用<code>run</code>方法，虽然我们是重写<code>Runnable</code>的 <code>run</code>方法的。调用 <code>run</code>方法并没有创建线程的效果，而是直接在当前线程执行，就和执行一个普通类的普通方法一模一样。</p>
<p>为什么要调用 <code>start()</code>方法呢，我们看看 <code>Thread</code>的 <code>start()</code>方法实现中，其实是调用了一个名称为 <code>start0()</code>的 native 方法，native 方法就不是用 Java 实现的了，而是在 JVM 层面的实现。</p>
<p>这个<code>start0</code>方法的主要逻辑就是启动一个操作系统线程，并和 JVM 线程绑定，开辟一些空间来存储线程状态和上下文的数据，然后执行绑定的 JVM 线程（也就是我们实现了 Runnable 的类）的 <code>run</code>方法的代码块，从而执行我们自定义的逻辑。</p>
<p>还可以用线程池的方式调用</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">ThreadFactory</span><span class="w"> </span><span class="n">namedThreadFactory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">ThreadFactoryBuilder</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="na">setNameFormat</span><span class="p">(</span><span class="s">&#34;thread-pool-%d&#34;</span><span class="p">).</span><span class="na">build</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">ExecutorService</span><span class="w"> </span><span class="n">singleThreadPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">1</span><span class="p">,</span><span class="w"> </span><span class="n">1</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">0L</span><span class="p">,</span><span class="w"> </span><span class="n">TimeUnit</span><span class="p">.</span><span class="na">MILLISECONDS</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">new</span><span class="w"> </span><span class="n">LinkedBlockingQueue</span><span class="o">&lt;</span><span class="n">Runnable</span><span class="o">&gt;</span><span class="p">(</span><span class="n">1024</span><span class="p">),</span><span class="w"> </span><span class="n">namedThreadFactory</span><span class="p">,</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">ThreadPoolExecutor</span><span class="p">.</span><span class="na">AbortPolicy</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">singleThreadPool</span><span class="p">.</span><span class="na">execute</span><span class="p">(</span><span class="n">runnableThread</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">singleThreadPool</span><span class="p">.</span><span class="na">shutdown</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><p>如果 <code>Runnable</code>那么完美的话，就没必要在 JDK1.5 中加入和它超级相似的 <code>Callable</code>了。</p>
<p><code>Runnable</code>有什么不完美的地方吗？就是它的 <code>run</code>方法是没有返回值的。</p>
<p>如果你想在主线程中拿到新开启线程的返回值的话，<code>Runnable</code>就不太方便了。必须要借助共享变量来完成。</p>
<p>所以，如果你的场景是要有返回值的话， 就要 <code>Callable</code>出手了。</p>
<h2 id="callable">Callable</h2>
<p><code>Callable</code>是在 JDK1.5 才加入的，为的就是弥补 <code>Runnable</code>没有返回值的缺陷，虽然绝大多数场景都可以用 <code>Runnable</code>来实现。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@FunctionalInterface</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">interface</span> <span class="nc">Callable</span><span class="o">&lt;</span><span class="n">V</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">V</span><span class="w"> </span><span class="nf">call</span><span class="p">()</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>和 <code>Runnable</code>类似的，<code>@FunctionalInterface</code>也是后来加入的，可以不考虑，只是为了函数式写法。</p>
<p><code>Callable</code>接口只有一个 <code>call</code>方法，并且有一个泛型返回值，可以返回任何类型。</p>
<h4 id="举个例子-1">举个例子</h4>
<p>首先声明一个类，实现自 <code>Callable</code>接口，返回值为字符串类型</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">CallableThread</span><span class="w"> </span><span class="kd">implements</span><span class="w"> </span><span class="n">Callable</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">call</span><span class="p">()</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;线程名称：&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">Thread</span><span class="p">.</span><span class="na">currentThread</span><span class="p">().</span><span class="na">getName</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>在代码中通过下面的方式调用</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">CallableThread</span><span class="w"> </span><span class="n">callableThread</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">CallableThread</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">FutureTask</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">futureTask</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">FutureTask</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">callableThread</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">Thread</span><span class="w"> </span><span class="n">thread</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">Thread</span><span class="p">(</span><span class="n">futureTask</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">thread</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">String</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">futureTask</span><span class="p">.</span><span class="na">get</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;执行结果= &#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">result</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><p>看上去就比 <code>Runnable</code>要复杂一点，要借助<code>FutureTask</code>了，因为 <code>Thread</code>类没有接受<code>Callable</code>的构造函数。</p>
<p>使用 <code>FutureTask.get()</code>方法获取执行结果。</p>
<p>在日常的开发中，不建议直接这样用，除非你明确的知道这样做没有问题，否则的话，推荐使用线程池的方式来使用。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">CallableThread</span><span class="w"> </span><span class="n">callableThread</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">CallableThread</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">ExecutorService</span><span class="w"> </span><span class="n">executor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Executors</span><span class="p">.</span><span class="na">newSingleThreadExecutor</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">Future</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">future</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">executor</span><span class="p">.</span><span class="na">submit</span><span class="p">(</span><span class="n">callableThread</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">String</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">future</span><span class="p">.</span><span class="na">get</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;任务执行结果: &#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">result</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">executor</span><span class="p">.</span><span class="na">shutdown</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><h2 id="如何选择用哪一个">如何选择用哪一个</h2>
<p>取舍的基本原则就是需不需要返回值，如果不需要返回值，那直接就选 <code>Runnable</code>，不用犹豫。如果有返回值的话，那更不用犹豫，不要想着借助共享变量的方式。</p>
<p>另外还有一点就是是否需要抛出异常， <code>Runnable</code>是不接受抛出异常的，<code>Callable</code>可以抛出异常。</p>
<p><code>Runnable</code>适合那种纯异步的处理逻辑。比如每天定时计算报表，将报表存储到数据库或者其他地方，只是要计算，不需要马上展示，展示内容是在其他的方法中单独获取的。</p>
<p>比如那些非核心的功能，当核心流程执行完毕后，非核心功能就自己去执行吧，至于成不成功的，不是特别重要。例如一个购物下单流程，下单、减库存、加到用户的订单列表、扣款是核心功能，之后的发送 APP 通知、短信通知这些就启动新线程去干去吧。</p>
<h2 id="最后">最后</h2>
<p><code>Runnable</code> 在 <code>java.lang</code>这个包下，而当 JDK1.5 发布的时候，新加入的 <code>Callable</code>被安置在了 <code>java.util.concurrent</code>这个包下，这是 Java 里有名的并发编程相关包，各种锁啊、多线程工具类啊，都被放在这个包下。按道理，<code>Runnable</code> 也应该在这里才对。</p>
<p>可见再厉害的项目也是随着项目的扩大而慢慢的规划，而前期的一些看似不太合理的地方，只能做兼容和妥协。</p>
]]></content:encoded></item><item><title>如何用 Docker 快速搭建开发环境</title><link>https://www.showcodeba.com/category/java/docker%E6%90%AD%E5%BB%BA%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83/</link><pubDate>Thu, 27 Jul 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/java/docker%E6%90%AD%E5%BB%BA%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83/</guid><description>如何用 Docker 快速搭建开发环境</description><content:encoded><![CDATA[<p><strong>大家好，我是风筝</strong></p>
<p>有些时候我们需要在本地搭开发环境，比如平时学习新技术的时候。或者有时候公司的项目需要在本地建一套类似的，方便调试修改。</p>
<p>开发环境可能包括 MySQL、Redis、Nginx、MQ 、Elasticsearch 等等，今天用的是 MySQL，明天又用 PostgreSQL 了，如果用啥都在本机装一下的话，倒也不是不可以，但是就非常麻烦了，而且有时候安装会遇到各种各样的问题，很是不爽。</p>
<p>有什么快速有优雅的方式没有？</p>
<p>就是我们今天要说的 Docker 了，Docker 到现在为止，早就不是什么新技术了，甚至可以说是很普遍很大众的技术方案了。</p>
<p>我记得我最早开始用 Docker 已经是 2015 年了，到现在已经快 8 年了。只能感叹，时间过得是真快呀！</p>
<p><img src="https://hexo.moonkite.cn/blog/81shots_so.png"></p>
<p>那段时间也把 Docker 安利给了不少同学、同事，我只是这样告诉他们：“这玩意儿在本地搭一套开发环境真的很方便”，至于它后来发展成现在的规模，我也是没想到的。</p>
<p>如果你是个开发老手儿，相信你早就用起来了。而如果你是个新手，那也要尽快用起来了，不仅能节省搭建环境的时间，还能学习学习，毕竟现在容器化部署方式很普遍了，多了解了解还是非常有必要的。</p>
<h2 id="docker-原理和基础命令">Docker 原理和基础命令</h2>
<h3 id="基本原理">基本原理</h3>
<p>用任何一种技术首先都要先了解它的技术原理，做到心中有数。</p>
<p>Docker 使用 Google 公司推出的 Go 语言进行开发实现，基于 Linux 内核的 cgroup，namespace，以及 OverlayFS 类的 Union FS 等技术，对进程进行封装隔离，属于操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程，因此也称其为容器。</p>
<p>Docker 经常拿来和虚拟机来比较，因为它们两个的用处和用法都很相似，就是在一台实体机上启动多个虚拟系统（暂且这么说）。如果基于使用的层面来讲，你完全可以把 Docker 当做虚拟机来用，然而实际的底层技术原理是完全不一样的。</p>
<p>假设你现在变身了，站在了 Docker 和 虚拟机的内部，从里面向外看，发现虚拟机有自己的 CPU(虚拟 CPU)、内存、硬盘，再往外才是宿主机的 CPU、硬盘、内存等。而如果是在 Docker 内部向外看，发现你无论站在当前实体机的哪个容器里，看到的都是宿主机的 CPU、硬盘、内存等。说明 Dokcer 容器是直接拿宿主机的资源当自己的用，所以每个容器的硬件配置都是一样的，而虚拟机是完全虚拟出来一套。</p>
<h3 id="基础命令">基础命令</h3>
<p>Dokcer 的命令有一大堆，但是常用的就那几个。</p>
<h4 id="镜像相关">镜像相关</h4>
<p><strong>获取镜像</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">docker pull 镜像名称
</span></span></code></pre></div><p><strong>查看镜像列表</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">docker image ls
</span></span></code></pre></div><h4 id="容器相关">容器相关</h4>
<p><strong>查看容器</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">docker ps -a
</span></span></code></pre></div><p><strong>新建并启动容器</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo docker run -t -i ubuntu:12.04  /bin/bash
</span></span></code></pre></div><p><strong>进入容器</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">docker <span class="nb">exec</span> -it 容器id bash
</span></span></code></pre></div><p>因为本文不是 Docker 的教程，所以只是抛砖引玉，具体的教程和文档网上到处都是。</p>
<h2 id="安装-docker-desktop">安装 Docker Desktop</h2>
<p>接下来开始本地实践了，用 Docker 快速搭一套本地环境。</p>
<p>有一些 Docker 的可视化客户端可以安装，帮我们更方便更直观的管理镜像、容器，当然如果你很厉害的话，完全用命令行也不是不行。</p>
<p>我就没这么厉害了，那些参数根本记不住，所以必须要装客户端。我安装的是 Docker Desktop，官方客户端，支持 Mac、Linux、Windows，我大部分时候用 Mac，有时候也要用 Windows，所以 Docker Desktop 是不二选择。</p>
<p>如果你只用 Mac 的话，也可以安装 <strong>OrbStack</strong>（https://orbstack.dev/），据说比 Docker Desktop 要流畅一些。</p>
<h3 id="下载安装">下载安装</h3>
<p>访问 Docker Desktop 官网 <a href="https://www.docker.com/products/docker-desktop/">https://www.docker.com/products/docker-desktop/</a>，根据你的系统下载对应的版本。</p>
<p><img alt="763shots_so" src="https://hexo.moonkite.cn/blog/763shots_so.png"></p>
<p>下载完一键安装就可以了，Docker 环境直接帮你装好了，省心省事儿。下面是它的控制台界面。左侧导航可以查看镜像列表、容器列表、映射的 Volumes。</p>
<p><img src="https://hexo.moonkite.cn/blog/425shots_so.png"></p>
<p>我们在命令行输入 <code>docker -v</code>，如果出现正确的版本号，说明 docker 服务已经安装正常了。</p>
<blockquote>
<p>Docker version 24.0.2, build cb74dfc</p>
</blockquote>
<p>接下来有一件事儿是必须要做的，那就是设置国内镜像源，道理大家都懂，你要是用国外的镜像源，一天都不一定能搞下来一个。</p>
<p>点击「设置」按钮，在左侧选择「Docker Engine」</p>
<p><img src="https://hexo.moonkite.cn/blog/21shots_so.png"></p>
<p>,然后在右侧的配置框中添加如下配置，加到最下面即可：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="s2">&#34;registry-mirrors&#34;</span>: <span class="o">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;https://docker.mirrors.ustc.edu.cn&#34;</span>,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;https://cr.console.aliyun.com/&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="o">]</span>
</span></span></code></pre></div><p>加完后，点击「Apply &amp; restart」，这时候会重启 Docker 服务。</p>
<h2 id="安装一个服务">安装一个服务</h2>
<p>MySQL、Nginx、Redis、Zookeeper 这些都是服务，每一个都可以按照这种方式安装，不管你的项目需要多少个服务，一个个安装就好了，很快的呦。</p>
<p>正好我最进要学一下 PostgreSQL，我就直接启动一个 docker 容器了，不在本地安装了。当然了，像数据库这种 I/O 型的应用是不建议容器化的，但是本地开发测试无所谓。</p>
<p>在这个搜索框输入关键词，查找对应的镜像，我在这里输入关键词 <code>PostgreSQL</code>。如果安装 Nginx 那就是输入 Nginx，以此类推。</p>
<p><img alt="search" src="https://hexo.moonkite.cn/blog/search.png"></p>
<p>看，出来了一大堆，我一般都是找第一个，也就是下载量最大的这个。</p>
<p><img src="https://hexo.moonkite.cn/blog/894-result.png"></p>
<p>可以 <code>pull</code> 或者 <code>run</code>，pull 是拉取镜像，run 是拉取镜像+启动容器。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230725173926556.png"></p>
<p>我一般都是直接 <code>run</code>，点一下 <code>run</code>，等一会儿，因为前面已经设置了国内镜像源，所以速度很快，十几秒钟。（根据网速快慢不同，因为要下载镜像文件，有的应用有上百 M）</p>
<p>下载完之后，因为选的是 <code>run</code>，所以直接弹出了启动参数。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230725181057855.png"></p>
<p><strong>设置镜像名称</strong></p>
<p>容器名称（Container name）就是为了我们一看到它就知道是干什么的，用来方便管理的，我就直接命名为 <code>PostgreSQL</code>了。</p>
<p><strong>设置端口映射（Ports）</strong></p>
<p>服务本身有自己的端口，例如 MySQL 是 3306，这是服务本身在容器内的启动端口，但最终我们要通过宿主机去访问服务，所以要将这个容器内的端口绑定到一个宿主机端口，这叫做端口映射，这样一来，我们就可以通过宿主机的端口访问到容器内的端口了。比如我将 MySQL 容器的 <code>3306</code> 端口映射到宿主机的 <code>13306</code> 端口，之后我在设置数据库连接的时候，就用本机 IP:13306 就能访问 MySQL 容器了。</p>
<p>在我这个 PostgreSQL 容器上，我将本机的 <code>15432</code>和容器的 <code>5432</code>端口绑定，之后就可以用 <code>15432</code>做连接端口了。</p>
<p><strong>目录映射（Volumes）</strong></p>
<p>很多服务都会用到存储目录，但是容器本身就在宿主机上，所以需要将服务在容器内的目录映射到宿主机的目录上，这叫目录映射。例如将 Nginx 容器的 <code>/etc/nginx</code>目录映射到宿主机的 <code>/apps/nginx/</code>目录上，那之后我在宿主机访问 <code>/apps/nginx</code>目录时，就能看到 Nginx 容器的配置文件了。</p>
<p>在我这个 PostgreSQL 容器上，我将 <code>/etc/postgresql/postgresql.conf</code>配置文件映射到了我本地的一个目录上。</p>
<p><strong>环境变量</strong></p>
<p>一个服务启动可能会用到启动变量，这些变量可以通过环境变量的方式进行配置。例如，我们启动一个 Java jar 包，要配置 JVM 相关的参数，这些参数就可以放到环境变量中，供启动的时候使用。</p>
<p>因为 PostgreSQL 需要密码，所以这里就设置一个密码的环境变量 <code>POSTGRES_PASSWORD</code>。</p>
<p>都设置好之后，点击<code>run</code>按钮，启动容器。</p>
<p>然后我们看到容器已经启动了，有相应的日志打印出来。</p>
<p><img src="https://hexo.moonkite.cn/blog/805shots_so.png"></p>
<p>然后点击左侧的<code>Containers</code>，可以看到已经启动的容器和一些基本配置。</p>
<p><img src="https://hexo.moonkite.cn/blog/478shots_so.png"></p>
<p>然后就可以通过客户端进行连接了。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230725183859472.png"></p>
<p><img src="https://hexo.moonkite.cn/blog/283shots_so.png"></p>
<p>另外，还可以在容器详情中查看容器的内部文件、状态、配置信息、日志等，以及进入命令行。</p>
<p><img src="https://hexo.moonkite.cn/blog/746shots_so.png"></p>
<h2 id="最后">最后</h2>
<p>第一次可能比较慢，熟悉了之后就非常快了，比你在本地直接安装配置要快的多，而且一些软件安装配置起来非常繁琐，会浪费很多不必要的时间。</p>
<p>还有很多开源的框架、应用提供 docker 版本，掌握了这个方法后，可以很快的安装这些框架、应用来验证和使用，用户体验直接上升一个数量级。</p>
]]></content:encoded></item><item><title>什么是零拷贝|Java中的零拷贝</title><link>https://www.showcodeba.com/category/java/%E4%BB%80%E4%B9%88%E6%98%AF%E9%9B%B6%E6%8B%B7%E8%B4%9D/</link><pubDate>Sun, 23 Jul 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/java/%E4%BB%80%E4%B9%88%E6%98%AF%E9%9B%B6%E6%8B%B7%E8%B4%9D/</guid><description>零拷贝是什么</description><content:encoded><![CDATA[<p><strong>大家好，我是风筝</strong></p>
<p>我们总会在各种地方看到零拷贝，那零拷贝到底是个什么东西。</p>
<p>接下来，让我们来理一理啊。</p>
<p>拷贝说的是计算机里的 I/O 操作，也就是数据的读写操作。计算机可是一个复杂的家伙，包括软件和硬件两大部分，软件主要指操作系统、驱动程序和应用程序。硬件那就多了，CPU、内存、硬盘等等一大堆东西。</p>
<p>这么复杂的设备要进行读写操作，其中繁琐和复杂程度可想而知。</p>
<h2 id="传统-io-的读写过程">传统 I/O 的读写过程</h2>
<p>如果要了解零拷贝，那就必须要知道一般情况下，计算机是如何读写数据的，我把这种情况称为传统 I/O。</p>
<p>数据读写的发起者是计算机中的应用程序，比如我们常用的浏览器、办公软件、音视频软件等。</p>
<p>而数据的来源呢，一般是硬盘、外部存储设备或者是网络套接字（也就是网络上的数据通过网口+网卡的处理）。</p>
<p>过程本来是很复杂的，所以大学课程里要通过《操作系统》、《计算机组成原理》来专门讲计算机的软硬件。</p>
<h3 id="简化版读操作流程">简化版读操作流程</h3>
<p>那么细的没办法讲来，所以，我们把这个读写过程简化一下，忽略大多数细节，只讲流程。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230719182852190.png"></p>
<p>上图是应用程序进行一次读操作的过程。</p>
<ol>
<li>应用程序先发起读操作，准备读取数据了；</li>
<li>内核将数据从硬盘或外部存储读取到内核缓冲区；</li>
<li>内核将数据从内核缓冲区拷贝到用户缓冲区；</li>
<li>应用程序读取用户缓冲区的数据进行处理加工；</li>
</ol>
<h3 id="详细的读写操作流程">详细的读写操作流程</h3>
<p>下面是一个更详细的 I/O 读写过程。这个图可好用极了，我会借助这个图来厘清 I/O 操作的一些基础但非常重要的概念。</p>
<p><img src="https://hexo.moonkite.cn/blog/%E4%BC%A0%E7%BB%9FIO%E8%AF%BB%E5%86%992.png"></p>
<p>先看一下这个图，上面红粉色部分是读操作，下面蓝色部分是写操作。</p>
<p>如果一下子看着有点儿迷糊的话，没关系，看看下面几个概念就清楚了。</p>
<h4 id="应用程序">应用程序</h4>
<p>就是安装在操作系统上的各种应用。</p>
<h4 id="系统内核">系统内核</h4>
<p>系统内核是一些列计算机的核心资源的集合，不仅包括 CPU、总线这些硬件设备，也包括进程管理、文件管理、内存管理、设备驱动、系统调用等一些列功能。</p>
<h4 id="外部存储">外部存储</h4>
<p>外部存储就是指硬盘、U 盘等外部存储介质。</p>
<h4 id="内核态">内核态</h4>
<ul>
<li>内核态是操作系统内核运行的模式，当操作系统内核执行特权指令时，处于内核态。</li>
<li>在内核态下，操作系统内核拥有最高权限，可以访问计算机的所有硬件资源和敏感数据，执行特权指令，控制系统的整体运行。</li>
<li>内核态提供了操作系统管理和控制计算机硬件的能力，它负责处理系统调用、中断、硬件异常等核心任务。</li>
</ul>
<h4 id="用户态">用户态</h4>
<p>这里的用户可以理解为应用程序，这个用户是对于计算机的内核而言的，对于内核来说，系统上的各种应用程序会发出指令来调用内核的资源，这时候，应用程序就是内核的用户。</p>
<ul>
<li>用户态是应用程序运行的模式，当应用程序执行普通的指令时，处于用户态。</li>
<li>在用户态下，应用程序只能访问自己的内存空间和受限的硬件资源，无法直接访问操作系统的敏感数据或控制计算机的硬件设备。</li>
<li>用户态提供了一种安全的运行环境，确保应用程序之间相互隔离，防止恶意程序对系统造成影响。</li>
</ul>
<h4 id="模式切换">模式切换</h4>
<p>计算机为了安全性考虑，区分了内核态和用户态，应用程序不能直接调用内核资源，必须要切换到内核态之后，让内核来调用，内核调用完资源，再返回给应用程序，这个时候，系统在切换会用户态，应用程序在用户态下才能处理数据。</p>
<p>上述过程其实一次读和一次写都分别发生了两次模式切换。</p>
<p><img src="https://hexo.moonkite.cn/blog/%E6%A8%A1%E6%80%81%E5%88%87%E6%8D%A2.png"></p>
<h4 id="内核缓冲区">内核缓冲区</h4>
<p>内核缓冲区指内存中专门用来给内核直接使用的内存空间。可以把它理解为应用程序和外部存储进行数据交互的一个中间介质。</p>
<p>应用程序想要读外部数据，要从这里读。应用程序想要写入外部存储，要通过内核缓冲区。</p>
<h4 id="用户缓冲区">用户缓冲区</h4>
<p>用户缓冲区可以理解为应用程序可以直接读写的内存空间。因为应用程序没法直接到内核读写数据， 所以应用程序想要处理数据，必须先通过用户缓冲区。</p>
<h4 id="磁盘缓冲区">磁盘缓冲区</h4>
<p>磁盘缓冲区是计算机内存中用于暂存从磁盘读取的数据或将数据写入磁盘之前的临时存储区域。它是一种优化磁盘 I/O 操作的机制，通过利用内存的快速访问速度，减少对慢速磁盘的频繁访问，提高数据读取和写入的性能和效率。</p>
<h4 id="pagecache">PageCache</h4>
<ul>
<li>PageCache 是 Linux 内核对文件系统进行缓存的一种机制。它使用空闲内存来缓存从文件系统读取的数据块，加速文件的读取和写入操作。</li>
<li>当应用程序或进程读取文件时，数据会首先从文件系统读取到 PageCache 中。如果之后再次读取相同的数据，就可以直接从 PageCache 中获取，避免了再次访问文件系统。</li>
<li>同样，当应用程序或进程将数据写入文件时，数据会先暂存到 PageCache 中，然后由 Linux 内核异步地将数据写入磁盘，从而提高写入操作的效率。</li>
</ul>
<h4 id="再说数据读写操作流程">再说数据读写操作流程</h4>
<p>上面弄明白了这几个概念后，再回过头看一下那个流程图，是不是就清楚多了。</p>
<h5 id="读操作">读操作</h5>
<ol>
<li>首先应用程序向内核发起读请求，这时候进行一次模式切换了，从用户态切换到内核态；</li>
<li>内核向外部存储或网络套接字发起读操作；</li>
<li>将数据写入磁盘缓冲区；</li>
<li>系统内核将数据从磁盘缓冲区拷贝到内核缓冲区，顺便再将一份（或者一部分）拷贝到 PageCache；</li>
<li>内核将数据拷贝到用户缓冲区，供应用程序处理。此时又进行一次模态切换，从内核态切换回用户态；</li>
</ol>
<h5 id="写操作">写操作</h5>
<ol>
<li>应用程序向内核发起写请求，这时候进行一次模式切换了，从用户态切换到内核态；</li>
<li>内核将要写入的数据从用户缓冲区拷贝到 PageCache，同时将数据拷贝到内核缓冲区；</li>
<li>然后内核将数据写入到磁盘缓冲区，从而写入磁盘，或者直接写入网络套接字。</li>
</ol>
<h2 id="瓶颈在哪里">瓶颈在哪里</h2>
<p>但是传统 I/O 有它的瓶颈，这才是零拷贝技术出现的缘由。瓶颈是啥呢，当然是性能问题，太慢了。尤其是在高并发场景下，I/O 性能经常会卡脖子。</p>
<p>那是什么地方耗时了呢？</p>
<h3 id="数据拷贝">数据拷贝</h3>
<p>在传统 I/O 中，数据的传输通常涉及多次数据拷贝。数据需要从应用程序的用户缓冲区复制到内核缓冲区，然后再从内核缓冲区复制到设备或网络缓冲区。这些数据拷贝过程导致了多次内存访问和数据复制，消耗了大量的 CPU 时间和内存带宽。</p>
<h3 id="用户态和内核态的切换">用户态和内核态的切换</h3>
<p>由于数据要经过内核缓冲区，导致数据在用户态和内核态之间来回切换，切换过程中会有上下文的切换，如此一来，大大增加了处理数据的复杂性和时间开销。</p>
<p>每一次操作耗费的时间虽然很小，但是当并发量高了以后，积少成多，也是不小的开销。所以要提高性能、减少开销就要从以上两个问题下手了。</p>
<p>这时候，零拷贝技术就出来解决问题了。</p>
<h2 id="什么是零拷贝">什么是零拷贝</h2>
<p>问题出来数据拷贝和模态切换上。</p>
<p>但既然是 I/O 操作，不可能没有数据拷贝的，只能减少拷贝的次数，还有就是尽量将数据存储在离应用程序（用户缓冲区）更近的地方。</p>
<p>而区分用户态和内核态有其他更重要的原因，不可能单纯为了 I/O 效率就改变这种设计吧。那也只能尽量减少切换的次数。</p>
<p>零拷贝的理想状态就是操作数据不用拷贝，但是显示情况下并不一定真的就是一次复制操作都没有，而是尽量减少拷贝操作的次数。</p>
<p>要实现零拷贝，应该从下面这三个方面入手：</p>
<ol>
<li>尽量减少数据在各个存储区域的复制操作，例如从磁盘缓冲区到内核缓冲区等；</li>
<li>尽量减少用户态和内核态的切换次数及上下文切换；</li>
<li>使用一些优化手段，例如对需要操作的数据先缓存起来，内核中的 PageCache 就是这个作用；</li>
</ol>
<h2 id="实现零拷贝方案">实现零拷贝方案</h2>
<h3 id="直接内存访问dma">直接内存访问（DMA）</h3>
<p>DMA 是一种硬件特性，允许外设（如网络适配器、磁盘控制器等）直接访问系统内存，而无需通过 CPU 的介入。在数据传输时，DMA 可以直接将数据从内存传输到外设，或者从外设传输数据到内存，避免了数据在用户态和内核态之间的多次拷贝。</p>
<p><img alt="DMA1" src="https://hexo.moonkite.cn/blog/DMA1.png"></p>
<p>如上图所示，内核将数据读取的大部分数据读取操作都交个了 DMA 控制器，而空出来的资源就可以去处理其他的任务了。</p>
<h3 id="sendfile">sendfile</h3>
<p>一些操作系统（例如 Linux）提供了特殊的系统调用，如 sendfile，在网络传输文件时实现零拷贝。通过 sendfile，应用程序可以直接将文件数据从文件系统传输到网络套接字或者目标文件，而无需经过用户缓冲区和内核缓冲区。</p>
<p>如果不用 sendfile，如果将 A 文件写入 B 文件。</p>
<ol>
<li>需要先将 A 文件的数据拷贝到内核缓冲区，再从内核缓冲区拷贝到用户缓冲区；</li>
<li>然后内核再将用户缓冲区的数据拷贝到内核缓冲区，之后才能写入到 B 文件；</li>
</ol>
<p>而用了 sendfile，用户缓冲区和内核缓冲区的拷贝都不用了，节省了一大部分的开销。</p>
<h3 id="共享内存">共享内存</h3>
<p>使用共享内存技术，应用程序和内核可以共享同一块内存区域，避免在用户态和内核态之间进行数据拷贝。应用程序可以直接将数据写入共享内存，然后内核可以直接从共享内存中读取数据进行传输，或者反之。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230721145324364.png"></p>
<p>通过共享一块儿内存区域，实现数据的共享。就像程序中的引用对象一样，实际上就是一个指针、一个地址。</p>
<h3 id="内存映射文件memory-mapped-files">内存映射文件（Memory-mapped Files）</h3>
<p>内存映射文件直接将磁盘文件映射到应用程序的地址空间，使得应用程序可以直接在内存中读取和写入文件数据，这样一来，对映射内容的修改就是直接的反应到实际的文件中。</p>
<p>当文件数据需要传输时，内核可以直接从内存映射区域读取数据进行传输，避免了数据在用户态和内核态之间的额外拷贝。</p>
<p>虽然看上去感觉和共享内存没什么差别，但是两者的实现方式完全不同，一个是共享地址，一个是映射文件内容。</p>
<h2 id="java-实现零拷贝的方式">Java 实现零拷贝的方式</h2>
<p>Java 标准的 IO 库是没有零拷贝方式的实现的，标准 IO 就相当于上面所说的传统模式。只是在 Java 推出的 NIO 中，才包含了一套新的 I/O 类，如 <code>ByteBuffer</code> 和 <code>Channel</code>，它们可以在一定程度上实现零拷贝。</p>
<p><code>ByteBuffer</code>：可以直接操作字节数据，避免了数据在用户态和内核态之间的复制。</p>
<p><code>Channel</code>：支持直接将数据从文件通道或网络通道传输到另一个通道，实现文件和网络的零拷贝传输。</p>
<p>借助这两种对象，结合 NIO 中的 API，我们就能在 Java 中实现零拷贝了。</p>
<p>首先我们先用传统 IO 写一个方法，用来和后面的 NIO 作对比，这个程序的目的很简单，就是将一个 100M 左右的 PDF 文件从一个目录拷贝到另一个目录。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">ioCopy</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">try</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">File</span><span class="w"> </span><span class="n">sourceFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">File</span><span class="p">(</span><span class="n">SOURCE_FILE_PATH</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">File</span><span class="w"> </span><span class="n">targetFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">File</span><span class="p">(</span><span class="n">TARGET_FILE_PATH</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="p">(</span><span class="n">FileInputStream</span><span class="w"> </span><span class="n">fis</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">FileInputStream</span><span class="p">(</span><span class="n">sourceFile</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">FileOutputStream</span><span class="w"> </span><span class="n">fos</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">FileOutputStream</span><span class="p">(</span><span class="n">targetFile</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="kt">byte</span><span class="o">[]</span><span class="w"> </span><span class="n">buffer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="kt">byte</span><span class="o">[</span><span class="n">1024</span><span class="o">]</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="kt">int</span><span class="w"> </span><span class="n">bytesRead</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">while</span><span class="w"> </span><span class="p">((</span><span class="n">bytesRead</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">fis</span><span class="p">.</span><span class="na">read</span><span class="p">(</span><span class="n">buffer</span><span class="p">))</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="o">-</span><span class="n">1</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">fos</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span><span class="w"> </span><span class="n">0</span><span class="p">,</span><span class="w"> </span><span class="n">bytesRead</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;传输 &#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">formatFileSize</span><span class="p">(</span><span class="n">sourceFile</span><span class="p">.</span><span class="na">length</span><span class="p">())</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">&#34; 字节到目标文件&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="n">IOException</span><span class="w"> </span><span class="n">e</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>下面是这个拷贝程序的执行结果，109.92M，耗时 1.29 秒。</p>
<blockquote>
<p>传输 109.92 M 字节到目标文件
耗时: 1.290 秒</p>
</blockquote>
<h3 id="filechanneltransferto-和-transferfrom">FileChannel.transferTo() 和 transferFrom()</h3>
<p>FileChannel 是一个用于文件读写、映射和操作的通道，同时它在并发环境下是线程安全的，基于 FileInputStream、FileOutputStream 或者 RandomAccessFile 的 getChannel() 方法可以创建并打开一个文件通道。FileChannel 定义了 transferFrom() 和 transferTo() 两个抽象方法，它通过在通道和通道之间建立连接实现数据传输的。</p>
<p>这两个方法首选用 sendfile 方式，只要当前操作系统支持，就用 sendfile，例如 Linux 或 MacOS。如果系统不支持，例如 windows，则采用内存映射文件的方式实现。</p>
<h4 id="transferto">transferTo()</h4>
<p>下面是一个 transferTo 的例子，仍然是拷贝那个 100M 左右的 PDF，我的系统是 MacOS。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">nioTransferTo</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">try</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">File</span><span class="w"> </span><span class="n">sourceFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">File</span><span class="p">(</span><span class="n">SOURCE_FILE_PATH</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">File</span><span class="w"> </span><span class="n">targetFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">File</span><span class="p">(</span><span class="n">TARGET_FILE_PATH</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="p">(</span><span class="n">FileChannel</span><span class="w"> </span><span class="n">sourceChannel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">RandomAccessFile</span><span class="p">(</span><span class="n">sourceFile</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;r&#34;</span><span class="p">).</span><span class="na">getChannel</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">FileChannel</span><span class="w"> </span><span class="n">targetChannel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">RandomAccessFile</span><span class="p">(</span><span class="n">targetFile</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;rw&#34;</span><span class="p">).</span><span class="na">getChannel</span><span class="p">())</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="kt">long</span><span class="w"> </span><span class="n">transferredBytes</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sourceChannel</span><span class="p">.</span><span class="na">transferTo</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="w"> </span><span class="n">sourceChannel</span><span class="p">.</span><span class="na">size</span><span class="p">(),</span><span class="w"> </span><span class="n">targetChannel</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;传输 &#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">formatFileSize</span><span class="p">(</span><span class="n">transferredBytes</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">&#34; 字节到目标文件&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="n">IOException</span><span class="w"> </span><span class="n">e</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>只耗时 0.536 秒，快了一倍。</p>
<blockquote>
<p>传输 109.92 M 字节到目标文件
耗时: 0.536 秒</p>
</blockquote>
<h4 id="transferfrom">transferFrom()</h4>
<p>下面是一个 transferFrom 的例子，仍然是拷贝那个 100M 左右的 PDF，我的系统是 MacOS。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">nioTransferFrom</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">try</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">File</span><span class="w"> </span><span class="n">sourceFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">File</span><span class="p">(</span><span class="n">SOURCE_FILE_PATH</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">File</span><span class="w"> </span><span class="n">targetFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">File</span><span class="p">(</span><span class="n">TARGET_FILE_PATH</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="p">(</span><span class="n">FileChannel</span><span class="w"> </span><span class="n">sourceChannel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">RandomAccessFile</span><span class="p">(</span><span class="n">sourceFile</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;r&#34;</span><span class="p">).</span><span class="na">getChannel</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">FileChannel</span><span class="w"> </span><span class="n">targetChannel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">RandomAccessFile</span><span class="p">(</span><span class="n">targetFile</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;rw&#34;</span><span class="p">).</span><span class="na">getChannel</span><span class="p">())</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="kt">long</span><span class="w"> </span><span class="n">transferredBytes</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">targetChannel</span><span class="p">.</span><span class="na">transferFrom</span><span class="p">(</span><span class="n">sourceChannel</span><span class="p">,</span><span class="w"> </span><span class="n">0</span><span class="p">,</span><span class="w"> </span><span class="n">sourceChannel</span><span class="p">.</span><span class="na">size</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;传输 &#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">formatFileSize</span><span class="p">(</span><span class="n">transferredBytes</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">&#34; 字节到目标文件&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="n">IOException</span><span class="w"> </span><span class="n">e</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>执行时间：</p>
<blockquote>
<p>传输 109.92 M 字节到目标文件
耗时: 0.603 秒</p>
</blockquote>
<h3 id="memory-mapped-files"><strong>Memory-Mapped Files</strong></h3>
<p>Java 的 NIO 也支持内存映射文件（Memory-mapped Files），通过 <code>FileChannel.map()</code> 实现。</p>
<p>下面是一个 <code>FileChannel.map()</code>的例子，仍然是拷贝那个 100M 左右的 PDF，我的系统是 MacOS。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">nioMap</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">File</span><span class="w"> </span><span class="n">sourceFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">File</span><span class="p">(</span><span class="n">SOURCE_FILE_PATH</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">File</span><span class="w"> </span><span class="n">targetFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">File</span><span class="p">(</span><span class="n">TARGET_FILE_PATH</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">try</span><span class="w"> </span><span class="p">(</span><span class="n">FileChannel</span><span class="w"> </span><span class="n">sourceChannel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">RandomAccessFile</span><span class="p">(</span><span class="n">sourceFile</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;r&#34;</span><span class="p">).</span><span class="na">getChannel</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                 </span><span class="n">FileChannel</span><span class="w"> </span><span class="n">targetChannel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">RandomAccessFile</span><span class="p">(</span><span class="n">targetFile</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;rw&#34;</span><span class="p">).</span><span class="na">getChannel</span><span class="p">())</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="kt">long</span><span class="w"> </span><span class="n">fileSize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sourceChannel</span><span class="p">.</span><span class="na">size</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">MappedByteBuffer</span><span class="w"> </span><span class="n">buffer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sourceChannel</span><span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">FileChannel</span><span class="p">.</span><span class="na">MapMode</span><span class="p">.</span><span class="na">READ_ONLY</span><span class="p">,</span><span class="w"> </span><span class="n">0</span><span class="p">,</span><span class="w"> </span><span class="n">fileSize</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">targetChannel</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;传输 &#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">formatFileSize</span><span class="p">(</span><span class="n">fileSize</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">&#34; 字节到目标文件&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="n">IOException</span><span class="w"> </span><span class="n">e</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>执行时间：</p>
<blockquote>
<p>传输 109.92 M 字节到目标文件
耗时: 0.663 秒</p>
</blockquote>
]]></content:encoded></item><item><title>剑走偏锋，无头浏览器是什么神奇的家伙</title><link>https://www.showcodeba.com/category/other/%E6%97%A0%E5%A4%B4%E6%B5%8F%E8%A7%88%E5%99%A8/</link><pubDate>Thu, 13 Jul 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/other/%E6%97%A0%E5%A4%B4%E6%B5%8F%E8%A7%88%E5%99%A8/</guid><description>浏览器是再熟悉不过的东西了，几乎每个人用过，比如 Chrome、FireFox、Safari，尤其是我们程序员，可谓开发最强辅助，摸鱼最好的伴</description><content:encoded><![CDATA[<p><img src="https://hexo.moonkite.cn/blog/%E6%97%A0%E5%A4%B4%E6%B5%8F%E8%A7%88%E5%99%A8.png"></p>
<p>浏览器是再熟悉不过的东西了，几乎每个人用过，比如 Chrome、FireFox、Safari，尤其是我们程序员，可谓开发最强辅助，摸鱼最好的伴侣。</p>
<p>浏览器能干的事儿，无头浏览器都能干，而且很多时候比标准浏览器还要更好用，而且能实现一些很好玩儿的功能，我们能借助无头浏览器比肩标准浏览器强大的功能，而且又能灵活的用程序控制的特性，做出一些很有意思的产品功能来，稍后我们细说。</p>
<h2 id="什么是浏览器">什么是浏览器</h2>
<p>关于浏览器还有一个很好玩儿的梗，对于一些对计算机、对互联网不太了解的同学，你跟他说浏览器，他/她就默认是百度了，因为好多小白的浏览器都设置了百度为默认页面。所以很多小白将浏览器和搜索引擎（99%是百度）划等号了。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230606155021528.png"></p>
<p>浏览器里我百分之99的时间都是用 Chrome，不过有一说一，这玩意是真耗内存，我基本上是十几、二十几个的 tab 开着，再加上几个 IDEA 进程，16G 的内存根本就不够耗的。</p>
<p>以 Chrome 浏览器为例，Chrome 由以下几部分组成：</p>
<ol>
<li>渲染引擎（Rendering Engine）：Chromium使用的渲染引擎主要有两个选项：WebKit和Blink。WebKit是最初由苹果开发的渲染引擎，后来被Google采用并继续开发。Blink则是Google从WebKit分支出来并进行独立开发的渲染引擎，目前Chromium主要使用Blink作为其默认的渲染引擎。</li>
<li>JavaScript引擎（JavaScript Engine）：Chromium使用V8引擎作为其JavaScript引擎。V8是由Google开发的高性能JavaScript引擎，它负责解析和执行网页中的JavaScript代码。</li>
<li>网络栈（Network Stack）：Chromium的网络栈负责处理网络通信。它支持各种网络协议，包括HTTP、HTTPS、WebSocket等，并提供了网络请求、响应处理和数据传输等功能。</li>
<li>布局引擎（Layout Engine）：Chromium使用布局引擎来计算网页中元素的位置和大小，并确定它们在屏幕上的布局。布局引擎将CSS样式应用于DOM元素，并计算它们的几何属性。</li>
<li>绘制引擎（Painting Engine）：绘制引擎负责将网页内容绘制到屏幕上，生成最终的图像。它使用图形库和硬件加速技术来高效地进行绘制操作。</li>
<li>用户界面（User Interface）：Chromium提供了用户界面的支持，包括地址栏、标签页、书签管理、设置等功能。它还提供了扩展和插件系统，允许用户根据自己的需求进行个性化定制。</li>
<li>其他组件：除了上述主要组件外，Chromium还包括其他一些辅助组件，如存储系统、安全模块、媒体处理、数据库支持等，以提供更全面的浏览器功能。</li>
</ol>
<p>Chrome 浏览器光源码就有十几个G，2000多万行代码，可见，要实现一个功能完善的浏览器是一项浩大的工程。</p>
<h2 id="什么是无头浏览器">什么是无头浏览器</h2>
<p>无头浏览器（Headless Browser）是一种浏览器程序，没有图形用户界面（GUI），但能够执行与普通浏览器相似的功能。无头浏览器能够加载和解析网页，执行JavaScript代码，处理网页事件，并提供对DOM（文档对象模型）的访问和操作能力。</p>
<p>与传统浏览器相比，无头浏览器的主要区别在于其没有可见的窗口或用户界面。这使得它在后台运行时，不会显示实际的浏览器窗口，从而节省了系统资源，并且可以更高效地执行自动化任务。</p>
<p>常见的无头浏览器包括Headless Chrome（Chrome的无头模式）、PhantomJS、Puppeteer（基于Chrome的无头浏览器库）等。它们提供了编程接口，使开发者能够通过代码自动化控制和操作浏览器行为。</p>
<p>无头浏览器其实就是看不见的浏览器，所有的操作都要通过代码调用 API 来控制，所以浏览器能干的事儿，无头浏览器都能干，而且很多事儿做起来比标准的浏览器更简单。</p>
<p>我举几个常用的功能来说明一下无头浏览器的主要使用场景</p>
<ol>
<li><strong>自动化测试：</strong> 无头浏览器可以模拟用户行为，执行自动化测试任务，例如对网页进行加载、表单填写、点击按钮、检查页面元素等。</li>
<li><strong>数据抓取：</strong> 无头浏览器可用于爬取网页数据，自动访问网站并提取所需的信息，用于数据分析、搜索引擎优化等。</li>
<li><strong>屏幕截图：</strong> 无头浏览器可以加载网页并生成网页的截图，用于生成快照、生成预览图像等。</li>
<li><strong>服务器端渲染：</strong> 无头浏览器可以用于服务器端渲染（Server-side Rendering），将动态生成的页面渲染为静态HTML，提供更好的性能和搜索引擎优化效果。</li>
<li>生成 PDF 文件：使用浏览器自带的生成 PDF 功能，将目标页面转换成 PDF 。</li>
</ol>
<h2 id="使用无头浏览器做一些好玩的功能">使用无头浏览器做一些好玩的功能</h2>
<p>开篇就说了使用无头浏览器可以实现一些好玩儿的功能，这些功能别看不大，但是使用场景还是很多的，有些开发者就是抓住这些小功能，开发出好用的产品，运气好的话还能赚到钱，尤其是在国外市场。（在国内做收费的产品确实不容易赚到钱）</p>
<p>下面我们就来介绍两个好玩儿而且有用的功能。</p>
<p>前面的自动化测试、服务端渲染就不说了。</p>
<p>自动化测试太专业了，一般用户用不到，只有开发者或者测试工程师用。</p>
<p>服务端渲染使用无头浏览器确实没必要，因为有太多成熟的方案了，连 React 都有服务端渲染的能力(RSC)。</p>
<h3 id="网页截图功能">网页截图功能</h3>
<p>我们可能见过一些网站提供下载文字卡片或者图文卡片的功能。比如读到一段想要分享的内容，选中之后将文本端所在的区域生成一张图片。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230606114154145.png"></p>
<p>其实就是通过调用浏览器自身的 API <code>page.screenshot</code>，可以对整个页面或者选定的区域生成图片。</p>
<p>通过这个方法，我们可以做一个浏览器插件，用户选定某个区域后，直接生成对应的图片。这类功能在手机APP上很常见，在浏览器上一搬的网站都不提供。</p>
<p>说到这儿好像和无头浏览器都没什么关系吧，这都是标准浏览器中做的事儿，用户已经打开了页面，在浏览器上操作自己看到的内容，顺理成章。</p>
<p>但是如果这个操作是批量的呢，或者是在后台静默完成的情况呢？</p>
<p>那就需要无头浏览器来出手了，无头浏览器虽然没有操作界面，但是也具备绘制引擎的完整功能，仍然可以生成图像，利用这个功能，就可以批量的、静默生成图像了，并且可以截取完整的网页或者部分区域。</p>
<p>Puppeteer 是无头浏览器中的佼佼者，提供了简单好用的 API ，不过是 nodejs 版的。</p>
<p>如果是用 Java 开发的话，有一个替代品，叫做 Jvppeteer，提供了和 Puppeteer  几乎一模一样的 API。</p>
<p>下面这段代码就展示了如何用 Jvppeteer 来实现网页的截图。</p>
<p>下面这个方法是对整个网页进行截图，只需要给定网页 url 和 最终的图片路径就可以了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">boolean</span><span class="w"> </span><span class="nf">screenShotWx</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">url</span><span class="p">,</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">path</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">IOException</span><span class="p">,</span><span class="w"> </span><span class="n">ExecutionException</span><span class="p">,</span><span class="w"> </span><span class="n">InterruptedException</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">BrowserFetcher</span><span class="p">.</span><span class="na">downloadIfNotExist</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">ArrayList</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">arrayList</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">// MacOS 要这样写，指定Chrome的位置</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">String</span><span class="w"> </span><span class="n">executablePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;/Applications/Google Chrome.app/Contents/MacOS/Google Chrome&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">LaunchOptions</span><span class="w"> </span><span class="n">options</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">LaunchOptionsBuilder</span><span class="p">().</span><span class="na">withExecutablePath</span><span class="p">(</span><span class="n">executablePath</span><span class="p">).</span><span class="na">withArgs</span><span class="p">(</span><span class="n">arrayList</span><span class="p">).</span><span class="na">withHeadless</span><span class="p">(</span><span class="kc">true</span><span class="p">).</span><span class="na">withIgnoreHTTPSErrors</span><span class="p">(</span><span class="kc">true</span><span class="p">).</span><span class="na">build</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">// Windows 和 Linux 这样就可以，不用指定 Chrome 的安装位置</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">//LaunchOptions options = new LaunchOptionsBuilder().withArgs(arrayList).withHeadless(true).withIgnoreHTTPSErrors(true).build();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--no-sandbox&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--disable-setuid-sandbox&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--ignore-certificate-errors&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--disable-gpu&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--disable-web-security&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--disable-infobars&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--disable-extensions&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--disable-bundled-ppapi-flash&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--allow-running-insecure-content&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--mute-audio&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Browser</span><span class="w"> </span><span class="n">browser</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Puppeteer</span><span class="p">.</span><span class="na">launch</span><span class="p">(</span><span class="n">options</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Page</span><span class="w"> </span><span class="n">page</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">browser</span><span class="p">.</span><span class="na">newPage</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">page</span><span class="p">.</span><span class="na">setJavaScriptEnabled</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">page</span><span class="p">.</span><span class="na">setUserAgent</span><span class="p">(</span><span class="s">&#34;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">page</span><span class="p">.</span><span class="na">setCacheEnabled</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">page</span><span class="p">.</span><span class="na">onConsole</span><span class="p">((</span><span class="n">msg</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">&#34;==&gt; {}&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">msg</span><span class="p">.</span><span class="na">text</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">PageNavigateOptions</span><span class="w"> </span><span class="n">pageNavigateOptions</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">PageNavigateOptions</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">pageNavigateOptions</span><span class="p">.</span><span class="na">setTimeout</span><span class="p">(</span><span class="n">1000000</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">//dom加载完毕就算导航完成</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">pageNavigateOptions</span><span class="p">.</span><span class="na">setWaitUntil</span><span class="p">(</span><span class="n">Collections</span><span class="p">.</span><span class="na">singletonList</span><span class="p">(</span><span class="s">&#34;domcontentloaded&#34;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">page</span><span class="p">.</span><span class="na">goTo</span><span class="p">(</span><span class="n">url</span><span class="p">,</span><span class="w"> </span><span class="n">pageNavigateOptions</span><span class="p">,</span><span class="w"> </span><span class="kc">true</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">autoScroll</span><span class="p">(</span><span class="n">page</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">ElementHandle</span><span class="w"> </span><span class="n">body</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">page</span><span class="p">.</span><span class="na">$</span><span class="p">(</span><span class="s">&#34;body&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kt">double</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">body</span><span class="p">.</span><span class="na">boundingBox</span><span class="p">().</span><span class="na">getWidth</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kt">double</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">body</span><span class="p">.</span><span class="na">boundingBox</span><span class="p">().</span><span class="na">getHeight</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Viewport</span><span class="w"> </span><span class="n">viewport</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">Viewport</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">viewport</span><span class="p">.</span><span class="na">setWidth</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="n">width</span><span class="p">);</span><span class="w"> </span><span class="c1">// 设置视口宽度</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">viewport</span><span class="p">.</span><span class="na">setHeight</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">100</span><span class="p">);</span><span class="w"> </span><span class="c1">// 设置视口高度</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">page</span><span class="p">.</span><span class="na">setViewport</span><span class="p">(</span><span class="n">viewport</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">ScreenshotOptions</span><span class="w"> </span><span class="n">screenshotOptions</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">ScreenshotOptions</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">screenshotOptions</span><span class="p">.</span><span class="na">setType</span><span class="p">(</span><span class="s">&#34;jpeg&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">screenshotOptions</span><span class="p">.</span><span class="na">setFullPage</span><span class="p">(</span><span class="n">Boolean</span><span class="p">.</span><span class="na">FALSE</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">//screenshotOptions.setClip(clip);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">screenshotOptions</span><span class="p">.</span><span class="na">setPath</span><span class="p">(</span><span class="n">path</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">screenshotOptions</span><span class="p">.</span><span class="na">setQuality</span><span class="p">(</span><span class="n">100</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">// 或者转换为 base64</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">//String base64Str = page.screenshot(screenshotOptions);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">//System.out.println(base64Str);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">browser</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>一个自动滚屏的方法。</p>
<p>虽然可以监听页面上的事件通知，比如 <code>domcontentloaded</code>，文档加载完成的通知，但是很多时候并不能监听到网页上的所有元素都加载完成了。对于那些滚动加载的页面，可以用这种方式模拟完全加载，加载完成之后再进行操作就可以了。</p>
<p>使用自动滚屏的操作，可以模拟我们人为的在界面上下拉滚动条的操作，随着滚动条的下拉，页面上的元素会自然的加载，不管是同步的还有延迟异步的，比如图片、图表等。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">autoScroll</span><span class="p">(</span><span class="n">Page</span><span class="w"> </span><span class="n">page</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">page</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">null</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">page</span><span class="p">.</span><span class="na">evaluate</span><span class="p">(</span><span class="s">&#34;() =&gt; {\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;   return new Promise((resolve, reject) =&gt; {\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;            //滚动的总高度\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;            let totalHeight = 0;\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;            //每次向下滚动的高度 500 px\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;            let distance = 500;\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;            let k = 0;\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;            let timeout = 1000;\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;            let url = window.location.href;\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;            let timer = setInterval(() =&gt; {\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;                //滚动条向下滚动 distance\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;                window.scrollBy(0, distance);\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;                totalHeight += distance;\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;                k++;\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;                console.log(`当前第${k}次滚动，页面高度: ${totalHeight}`);\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;                //页面的高度 包含滚动高度\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;                let scrollHeight = document.body.scrollHeight;\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;                //当滚动的总高度 大于 页面高度 说明滚到底了。也就是说到滚动条滚到底时，以上还会继续累加，直到超过页面高度\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;                if (totalHeight &gt;= scrollHeight || k &gt;= 200) {\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;                    clearInterval(timer);\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;                    resolve();\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;                    window.scrollTo(0, 0);\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;                }\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;            }, timeout);\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;        })\n&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;  }&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="n">Exception</span><span class="w"> </span><span class="n">e</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>调用截图方法截图，这里是对一篇公众号文章进行整个网页的截图。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">screenShotWx</span><span class="p">(</span><span class="s">&#34;https://mp.weixin.qq.com/s/MzCyWqcH1TCytpnHI8dVjA&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;/Users/fengzheng/Desktop/PICTURE/wx.jpeg&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>或者也可以截取页面中的部分区域，比如某篇文章的正文部分，下面这个方法是截图一个博客文章的正文部分。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">boolean</span><span class="w"> </span><span class="nf">screenShotJueJin</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">url</span><span class="p">,</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">path</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">IOException</span><span class="p">,</span><span class="w"> </span><span class="n">ExecutionException</span><span class="p">,</span><span class="w"> </span><span class="n">InterruptedException</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">BrowserFetcher</span><span class="p">.</span><span class="na">downloadIfNotExist</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">ArrayList</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">arrayList</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">String</span><span class="w"> </span><span class="n">executablePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;/Applications/Google Chrome.app/Contents/MacOS/Google Chrome&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">LaunchOptions</span><span class="w"> </span><span class="n">options</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">LaunchOptionsBuilder</span><span class="p">().</span><span class="na">withExecutablePath</span><span class="p">(</span><span class="n">executablePath</span><span class="p">).</span><span class="na">withArgs</span><span class="p">(</span><span class="n">arrayList</span><span class="p">).</span><span class="na">withHeadless</span><span class="p">(</span><span class="kc">true</span><span class="p">).</span><span class="na">withIgnoreHTTPSErrors</span><span class="p">(</span><span class="kc">true</span><span class="p">).</span><span class="na">build</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">//LaunchOptions options = new LaunchOptionsBuilder().withArgs(arrayList).withHeadless(true).withIgnoreHTTPSErrors(true).build();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--no-sandbox&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--disable-setuid-sandbox&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Browser</span><span class="w"> </span><span class="n">browser</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Puppeteer</span><span class="p">.</span><span class="na">launch</span><span class="p">(</span><span class="n">options</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Page</span><span class="w"> </span><span class="n">page</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">browser</span><span class="p">.</span><span class="na">newPage</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">PageNavigateOptions</span><span class="w"> </span><span class="n">pageNavigateOptions</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">PageNavigateOptions</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">pageNavigateOptions</span><span class="p">.</span><span class="na">setTimeout</span><span class="p">(</span><span class="n">1000000</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">//dom加载完毕就算导航完成</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">pageNavigateOptions</span><span class="p">.</span><span class="na">setWaitUntil</span><span class="p">(</span><span class="n">Collections</span><span class="p">.</span><span class="na">singletonList</span><span class="p">(</span><span class="s">&#34;domcontentloaded&#34;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">page</span><span class="p">.</span><span class="na">goTo</span><span class="p">(</span><span class="n">url</span><span class="p">,</span><span class="w"> </span><span class="n">pageNavigateOptions</span><span class="p">,</span><span class="w"> </span><span class="kc">true</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">WaitForSelectorOptions</span><span class="w"> </span><span class="n">waitForSelectorOptions</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">WaitForSelectorOptions</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">waitForSelectorOptions</span><span class="p">.</span><span class="na">setTimeout</span><span class="p">(</span><span class="n">1000</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">15</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">waitForSelectorOptions</span><span class="p">.</span><span class="na">setVisible</span><span class="p">(</span><span class="n">Boolean</span><span class="p">.</span><span class="na">TRUE</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">// 指定截图的区域</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">ElementHandle</span><span class="w"> </span><span class="n">elementHandle</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">page</span><span class="p">.</span><span class="na">waitForSelector</span><span class="p">(</span><span class="s">&#34;article.article&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">waitForSelectorOptions</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Clip</span><span class="w"> </span><span class="n">clip</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">elementHandle</span><span class="p">.</span><span class="na">boundingBox</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Viewport</span><span class="w"> </span><span class="n">viewport</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">Viewport</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">ElementHandle</span><span class="w"> </span><span class="n">body</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">page</span><span class="p">.</span><span class="na">$</span><span class="p">(</span><span class="s">&#34;body&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kt">double</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">body</span><span class="p">.</span><span class="na">boundingBox</span><span class="p">().</span><span class="na">getWidth</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">viewport</span><span class="p">.</span><span class="na">setWidth</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="n">width</span><span class="p">);</span><span class="w"> </span><span class="c1">// 设置视口宽度</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">viewport</span><span class="p">.</span><span class="na">setHeight</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="n">clip</span><span class="p">.</span><span class="na">getHeight</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">100</span><span class="p">);</span><span class="w"> </span><span class="c1">// 设置视口高度</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">page</span><span class="p">.</span><span class="na">setViewport</span><span class="p">(</span><span class="n">viewport</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">ScreenshotOptions</span><span class="w"> </span><span class="n">screenshotOptions</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">ScreenshotOptions</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">screenshotOptions</span><span class="p">.</span><span class="na">setType</span><span class="p">(</span><span class="s">&#34;jpeg&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">screenshotOptions</span><span class="p">.</span><span class="na">setFullPage</span><span class="p">(</span><span class="n">Boolean</span><span class="p">.</span><span class="na">FALSE</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">screenshotOptions</span><span class="p">.</span><span class="na">setClip</span><span class="p">(</span><span class="n">clip</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">screenshotOptions</span><span class="p">.</span><span class="na">setPath</span><span class="p">(</span><span class="n">path</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">screenshotOptions</span><span class="p">.</span><span class="na">setQuality</span><span class="p">(</span><span class="n">100</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">// 或者生成图片的 base64编码</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">String</span><span class="w"> </span><span class="n">base64Str</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">page</span><span class="p">.</span><span class="na">screenshot</span><span class="p">(</span><span class="n">screenshotOptions</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="n">base64Str</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>调用方式：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">screenShotJueJin</span><span class="p">(</span><span class="s">&#34;https://juejin.cn/post/7239715628172902437&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;/Users/fengzheng/Desktop/PICTURE/juejin.jpeg&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>最后的效果是这样的，可以达到很清晰的效果。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230606143425476.png"></p>
<h3 id="网页生成-pdf-功能">网页生成 PDF 功能</h3>
<p>这个功能可太有用了，可以把一些网页转成离线版的文档。有人说直接保存网页不就行了，除了程序员，大部分人还是更能直接读 PDF ，而不会用离线存储的网页。</p>
<p>我们可以在浏览器上使用浏览器的「打印」功能，用来将网页转换成 PDF 格式。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230606144145679.png"></p>
<p>但这是直接在页面上操作，如果是批量操作呢，比如想把一个专栏的所有文章都生成 PDF呢，就可以用无头浏览器来做了。</p>
<p>有的同学说，用其他的库也可以呀，Java 里面有很多生成 PDF 的开源库，可以把 HTML 转成 PDF，比如Apache PDFBox、IText 等，但是这些库应对一般的场景还行，对于那种页面上有延迟加载的图表啊、图片啊、脚本之类的就束手无策了。</p>
<p>而无头浏览器就可以，你可以监听页面加载完成的事件，可以模拟操作，主动触发页面加载，甚至还可以在页面中添加自定义的样式、脚本等，让生成的 PDF 更加完整、美观。</p>
<p>下面这个方法演示了如何将一个网页转成 PDF 。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">boolean</span><span class="w"> </span><span class="nf">pdf</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">url</span><span class="p">,</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">savePath</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Browser</span><span class="w"> </span><span class="n">browser</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">null</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Page</span><span class="w"> </span><span class="n">page</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">null</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">try</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//自动下载，第一次下载后不会再下载</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">BrowserFetcher</span><span class="p">.</span><span class="na">downloadIfNotExist</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">ArrayList</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">arrayList</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// MacOS</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="n">executablePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;/Applications/Google Chrome.app/Contents/MacOS/Google Chrome&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">LaunchOptions</span><span class="w"> </span><span class="n">options</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">LaunchOptionsBuilder</span><span class="p">().</span><span class="na">withExecutablePath</span><span class="p">(</span><span class="n">executablePath</span><span class="p">).</span><span class="na">withArgs</span><span class="p">(</span><span class="n">arrayList</span><span class="p">).</span><span class="na">withHeadless</span><span class="p">(</span><span class="kc">true</span><span class="p">).</span><span class="na">withIgnoreHTTPSErrors</span><span class="p">(</span><span class="kc">true</span><span class="p">).</span><span class="na">build</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="c1">// windows 或 linux</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//LaunchOptions options = new LaunchOptionsBuilder().withArgs(arrayList).withHeadless(true).withIgnoreHTTPSErrors(true).build();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--no-sandbox&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--disable-setuid-sandbox&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--ignore-certificate-errors&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--disable-gpu&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--disable-web-security&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--disable-infobars&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--disable-extensions&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--disable-bundled-ppapi-flash&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--allow-running-insecure-content&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">arrayList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;--mute-audio&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">browser</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Puppeteer</span><span class="p">.</span><span class="na">launch</span><span class="p">(</span><span class="n">options</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">page</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">browser</span><span class="p">.</span><span class="na">newPage</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">page</span><span class="p">.</span><span class="na">onConsole</span><span class="p">((</span><span class="n">msg</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">&#34;==&gt; {}&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">msg</span><span class="p">.</span><span class="na">text</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">page</span><span class="p">.</span><span class="na">setViewport</span><span class="p">(</span><span class="n">viewport</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">page</span><span class="p">.</span><span class="na">setJavaScriptEnabled</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">page</span><span class="p">.</span><span class="na">setUserAgent</span><span class="p">(</span><span class="s">&#34;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">page</span><span class="p">.</span><span class="na">setCacheEnabled</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//设置参数防止检测</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">page</span><span class="p">.</span><span class="na">evaluateOnNewDocument</span><span class="p">(</span><span class="s">&#34;() =&gt;{ Object.defineProperties(navigator,{ webdriver:{ get: () =&gt; undefined } }) }&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">page</span><span class="p">.</span><span class="na">evaluateOnNewDocument</span><span class="p">(</span><span class="s">&#34;() =&gt;{ window.navigator.chrome = { runtime: {},  }; }&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">page</span><span class="p">.</span><span class="na">evaluateOnNewDocument</span><span class="p">(</span><span class="s">&#34;() =&gt;{ Object.defineProperty(navigator, &#39;languages&#39;, { get: () =&gt; [&#39;en-US&#39;, &#39;en&#39;] }); }&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">page</span><span class="p">.</span><span class="na">evaluateOnNewDocument</span><span class="p">(</span><span class="s">&#34;() =&gt;{ Object.defineProperty(navigator, &#39;plugins&#39;, { get: () =&gt; [1, 2, 3, 4, 5,6], }); }&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">PageNavigateOptions</span><span class="w"> </span><span class="n">pageNavigateOptions</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">PageNavigateOptions</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">pageNavigateOptions</span><span class="p">.</span><span class="na">setTimeout</span><span class="p">(</span><span class="n">1000000</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//dom加载完毕就算导航完成</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">pageNavigateOptions</span><span class="p">.</span><span class="na">setWaitUntil</span><span class="p">(</span><span class="n">Collections</span><span class="p">.</span><span class="na">singletonList</span><span class="p">(</span><span class="s">&#34;domcontentloaded&#34;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">page</span><span class="p">.</span><span class="na">goTo</span><span class="p">(</span><span class="n">url</span><span class="p">,</span><span class="w"> </span><span class="n">pageNavigateOptions</span><span class="p">,</span><span class="w"> </span><span class="kc">true</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 添加自定义演示</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">StyleTagOptions</span><span class="w"> </span><span class="n">styleTagOptions1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">StyleTagOptions</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">styleTagOptions1</span><span class="p">.</span><span class="na">setContent</span><span class="p">(</span><span class="s">&#34;html {-webkit-print-color-adjust: exact} .table &gt; table &gt; tr:nth-child(1),.table &gt; table &gt; tr:nth-child(2) {background: #4074b0;} #tableB td:nth-child(2) {width:60%;}&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">page</span><span class="p">.</span><span class="na">addStyleTag</span><span class="p">(</span><span class="n">styleTagOptions1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//滚屏</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">autoScroll</span><span class="p">(</span><span class="n">page</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Thread</span><span class="p">.</span><span class="na">sleep</span><span class="p">(</span><span class="n">1000</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">PDFOptions</span><span class="w"> </span><span class="n">pdfOptions</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">PDFOptions</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//            pdfOptions.setHeight(&#34;5200&#34;);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">pdfOptions</span><span class="p">.</span><span class="na">setPath</span><span class="p">(</span><span class="n">savePath</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">page</span><span class="p">.</span><span class="na">pdf</span><span class="p">(</span><span class="n">pdfOptions</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="n">Exception</span><span class="w"> </span><span class="n">e</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">log</span><span class="p">.</span><span class="na">error</span><span class="p">(</span><span class="s">&#34;生成pdf异常：{}&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w"> </span><span class="k">finally</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">page</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">null</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">page</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">browser</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">null</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">browser</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>调用生成 PDF 的方法，将一个微信公众号文章转成 PDF。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">pdfPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;/Users/fengzheng/Desktop/PDF&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">filePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pdfPath</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">&#34;/hello.pdf&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			 </span><span class="n">JvppeteerUtils</span><span class="p">.</span><span class="na">pdf</span><span class="p">(</span><span class="s">&#34;https://mp.weixin.qq.com/s/MzCyWqcH1TCytpnHI8dVjA&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">filePath</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>最终的效果，很清晰，样式都在，基本和页面一模一样。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230606150457020.png"></p>
]]></content:encoded></item><item><title>向量数据库是什么</title><link>https://www.showcodeba.com/category/other/%E5%90%91%E9%87%8F%E6%95%B0%E6%8D%AE%E5%BA%93%E6%98%AF%E4%BB%80%E4%B9%88/</link><pubDate>Sun, 02 Jul 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/other/%E5%90%91%E9%87%8F%E6%95%B0%E6%8D%AE%E5%BA%93%E6%98%AF%E4%BB%80%E4%B9%88/</guid><description>最近有朋友面试的时候被面试官问了有关向量数据库的问题，朋友说啥是向量数据库，咋没听过呢。 最近 ChatGPT 以及类似的 AI 产品大火，不仅带火了英伟达的 GPU 芯片</description><content:encoded><![CDATA[<p>最近有朋友面试的时候被面试官问了有关向量数据库的问题，朋友说啥是向量数据库，咋没听过呢。</p>
<p>最近 ChatGPT 以及类似的 AI 产品大火，不仅带火了英伟达的 GPU 芯片，也带火了向量数据库。</p>
<p>其实向量数据库吧，很早就之前就有了，而且很多公司也在用，只不过最近借着AI的东风，被推到了台前，比如 Pinecone 这家向量数据库公司，估值约为 10 亿美元，刚刚融资 1 亿美元。</p>
<p>再比如，这个&hellip;&hellip;</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230625163312478.png"></p>
<h2 id="什么是向量和向量化">什么是向量和向量化</h2>
<p>向量数据库，顾名思义，存储的内容是向量。</p>
<p>那什么又是向量呢？</p>
<p>我们最早接触向量应该是在数学里面，例如一个二维的向量用(x,y)表示在x轴和y轴的值，用(x,y,z)表是在x轴、y轴和z轴的三维向量。还可以有更多的维度表示多维向量。</p>
<p>向量数据库应用最多的场景就是相似度搜索，就是根据用户的一个输入匹配出几个相似度最高的结果。有点儿像模糊查询或者全文检索的意思，但是原理又不一样，全文检索的核心技术应该是分词+索引 ，而向量数据库是向量化（Embedding）+索引。</p>
<p>索引就不用多说了，只要涉及到存储的，一定会用到索引。</p>
<h3 id="向量化embedding">向量化（Embedding）</h3>
<p>在 OpenAI 官网上专门有 Embedding 的介绍，并且提供了相关的模型API，叫做 ada，专门将数据 Embedding。</p>
<p>Embedding 是用一个低维稠密向量来表示一个对象，使得这个向量能够表达相应对象的某些特征，同时向量之间的距离能反应对象之间的相似性。</p>
<p>Embedding 是一个多维向量数组，由一系列数字组成，通过相关的算法可以将文本、音频、视频等内容转换为多维向量数组，并最终存储到向量数据库中。</p>
<p>假设 <code>man</code>的向量数组可表示为 [0.1,0.2,0.1]，<code>woman</code>的向量数组可表示为[0.3,0.1,0.1]。</p>
<p>下图中可以看到这些多维向量在向量空间中的关系，man和woman之间、king和queen之间，China和Beijing之间。</p>
<p><img src="https://hexo.moonkite.cn/blog/Fvp0qCEXgAA6bnU.jpeg"></p>
<p>各种各样的内容进行向量化之后，最终在向量空间会形成或近或疏的关系。比如下面这幅图中可以看出，如果你搜索一个动物相关的内容，肯定（大概率）不会匹配到运动员的那一堆数据中。</p>
<p><img src="https://hexo.moonkite.cn/blog/1E2148AA-E0A2-4953-99F5-6E5255AC16D4.png"></p>
<p>向量化的过程是一个复杂的过程，通常会用到经过大量训练和优化的大模型以及神经网络等厉害的技术，所以，个人或普通公司只能用大厂提供的付费服务，例如OpenAI 的 Ada 模型。</p>
<p>为什么这么复杂呢，比如基于自然语言处理的方式向量化，不仅要分析文本本身的意思，还会包括情感分析、翻译等工作要处理，例如你搜索英文的 <code>apple</code>,也要能匹配上中文的<code>苹果</code>或其他的语种。还有如果你搜索<code>苹果太难吃了</code>，要不能匹配出<code>苹果太棒了</code>这种结果吧。</p>
<p>目前主流的Embedding 方法主要有如下这三类：</p>
<h4 id="矩阵分解法">矩阵分解法</h4>
<p>矩阵分解法是一种常见的 Embedding 方法，它可以将高维的矩阵映射成两个低维矩阵的乘积，很好地解决了数据稀疏的问题。</p>
<h4 id="基于自然语言处理的方法">基于自然语言处理的方法</h4>
<p>自然语言处理（NLP）是一种人工智能技术，它涉及计算机对人类语言的理解和生成。NLP 包括许多任务，如文本分类、情感分析、命名实体识别、机器翻译和自动摘要等。NLP 技术可以帮助计算机处理和分析大量的文本数据，从而使计算机能够更好地理解人类语言并作出更准确的预测和决策。而基于 NLP 的 Embedding 方法的主要思想是将每个单词或短语映射到一个低维向量空间中，使得在这个向量空间中，相似的单词或短语在距离上更加接近，以便于支持语义查找与分析工作。常见的方法包括：</p>
<ol>
<li>Word2vec</li>
<li>GloVe（Global Vectors for Word Representation）</li>
<li>FastText</li>
</ol>
<h4 id="基于图的方法">基于图的方法</h4>
<p>上述方法都是针对序列文本而设计的，而对于诸如社交网络分析、推荐系统、知识图谱等领域中的问题，如下图所示，数据对象之间更多呈现出图结构：</p>
<p><img src="https://hexo.moonkite.cn/blog/graph-data.png"></p>
<p>基于图的 Embedding 技术是一种将图数据（通常为高维稠密的矩阵）映射为低微稠密向量的过程，能够很好地解决图数据难以高效输入机器学习算法的问题。Graph Embedding 技术将图中的节点以低维稠密向量的形式进行表达，要求在原始图中相似 (不同的方法对相似的定义不同) 的节点其在低维表达空间也接近。常见的方法包括：</p>
<ol>
<li>DeepWalk</li>
<li>Node2vec</li>
<li>Metapath2vec</li>
</ol>
<h2 id="向量数据库的特点">向量数据库的特点</h2>
<p>向量数据库的应用场景决定了其大概率需要存储海量的数据，而不是想关系型数据库那样存储确定的多少条数据。</p>
<ul>
<li>向量数据库要具具有高可用、高扩展性的架构；</li>
<li>向量数据库是计算密集型应用，需要良好的硬件设备加速；</li>
<li>高并发、低延迟</li>
</ul>
<h2 id="应用场景">应用场景</h2>
<p>向量数据库的核心功能就是相似性匹配，所以，它的应用场景也是围绕着这个功能来的。</p>
<ul>
<li>文本搜索（包含语义的那种），最常见的功能；</li>
<li>图片、语音、视频搜索，例如那种以图搜图的功能，当然，与之匹配的向量化的过程也比文字更复杂，例如语音的向量化要包括对语音进行特征化的提取，比如声纹等等。歌曲识别（非歌词的那种）就可以用向量化和向量数据库；</li>
<li>推荐系统，根据系统给用户打的标签，给用户推荐相似度最高的商品、服务等；</li>
<li>异常检测，相似度过低可以判断为异常，例如人脸识别功能，如果相似度过低，那可能就是非本人；</li>
</ul>
<p>最近的AI热潮掀起了不少新技术，感觉快要学不过来，不过大多数的内容想学也学不会了，只能学学周边技术，用用人家的API了。</p>
<p>毕竟像OpenAI这种，里面的员工的职位都是xxx科学家，不是xxx工程师了。</p>
<p>但是，该关注还是要关注一下，说不好哪天就用到自己的产品里了。</p>
]]></content:encoded></item><item><title>RPC框架的核心是什么？</title><link>https://www.showcodeba.com/category/java/RPC-%E6%A6%82%E8%A7%88/</link><pubDate>Sat, 01 Jul 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/java/RPC-%E6%A6%82%E8%A7%88/</guid><description>什么是 RPC 首先思考这样一个问题，假设你不知道任何框架，现在有两台机器，每台机器上有一个服务，让你在其中的一个服务中调用另一个服务中的接口，怎么</description><content:encoded><![CDATA[<h2 id="什么是-rpc">什么是 RPC</h2>
<p>首先思考这样一个问题，假设你不知道任何框架，现在有两台机器，每台机器上有一个服务，让你在其中的一个服务中调用另一个服务中的接口，怎么样才能做到呢？</p>
<p>RPC 全称为远程过程调用（Remote Procedure Call Protocol），通俗点来说，就是从一台机器通过网络调用到另一台机器的某个方法。</p>
<p>那这样说来的话，通过 Socket 调用另一台机器的 Sokcet 服务也是RPC，通过一些 HTTP 请求工具包调用一个远程 HTTP 接口也是 RPC 了。</p>
<p>最开始的 RPC 定义还有可能是这样的，但是这种方式太底层、太繁琐了。</p>
<p>现在的 RPC 除了要有明显的网络通信特征外，还要具备下面几个特点：</p>
<p>1、良好的封装：让我们感觉调用远程接口就好像是调用本地方法一样方便，封装底层网络通信的细节，让我们更专注于业务逻辑。</p>
<p>2、良好的协议设计：数据在调用方和提供方传输，数据组织要遵循约定好的协议格式，比如 HTTP 协议、TCP 协议这种。</p>
<p>3、高效的序列化和反序列化：远程调用，所以很大一部分开销来自于数据传输，所以原则上数据量越小越好，这就依赖于序列化的算法了。</p>
<h2 id="封装的艺术如何隐藏底层逻辑">封装的艺术（如何隐藏底层逻辑）</h2>
<p>如果每次调用提供方提供的方法时都像下面这样繁琐，指明 URL、自己构造 JSON 序列化字符串等等，会不会不太方便。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="err">public</span> <span class="err">static</span> <span class="err">final</span> <span class="err">MediaType</span> <span class="err">JSON</span>
</span></span><span class="line"><span class="cl">    <span class="err">=</span> <span class="err">MediaType.get(</span><span class="s2">&#34;application/json; charset=utf-8&#34;</span><span class="err">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="err">OkHttpClient</span> <span class="err">client</span> <span class="err">=</span> <span class="err">new</span> <span class="err">OkHttpClient();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="err">String</span> <span class="err">post(String</span> <span class="err">url,</span> <span class="err">String</span> <span class="err">json)</span> <span class="err">throws</span> <span class="err">IOException</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="err">RequestBody</span> <span class="err">body</span> <span class="err">=</span> <span class="err">RequestBody.create(json,</span> <span class="err">JSON);</span>
</span></span><span class="line"><span class="cl">  <span class="err">Request</span> <span class="err">request</span> <span class="err">=</span> <span class="err">new</span> <span class="err">Request.Builder()</span>
</span></span><span class="line"><span class="cl">      <span class="err">.url(url)</span>
</span></span><span class="line"><span class="cl">      <span class="err">.post(body)</span>
</span></span><span class="line"><span class="cl">      <span class="err">.build();</span>
</span></span><span class="line"><span class="cl">  <span class="err">try</span> <span class="err">(Response</span> <span class="err">response</span> <span class="err">=</span> <span class="err">client.newCall(request).execute())</span> <span class="err">{</span>
</span></span><span class="line"><span class="cl">    <span class="err">return</span> <span class="err">response.body().string();</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="err">}</span>
</span></span></code></pre></div><p>而像 Dubbo 这样调用，如果没有接触过 RPC 的人，刚看到这段代码会感觉到好像就是在调用本地的一个方法。隐藏了很多的细节。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@DubboReference</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">private</span><span class="w"> </span><span class="n">DemoService</span><span class="w"> </span><span class="n">demoService</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">String</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">demoService</span><span class="p">.</span><span class="na">sayHello</span><span class="p">(</span><span class="s">&#34;world&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;Receive result ======&gt; &#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">result</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><p>这就是现代 RPC 里一个很重要的特性，就是封装，尽量隐藏网络上的细节，让使用者感觉就是在调用本地的方法。</p>
<p><img src="https://hexo.moonkite.cn/blog/RPC1.png"></p>
<p>那在一般的 RPC 框架中，例如 Dubbo、GRPC等，是如何进行封装的呢。</p>
<p>我们看上面的图，其中调用方和服务提供方都有一个部分叫做存根（Stub），就是靠的它。听上去感觉这词不太容易理解啊，啥叫存根呢，其实说简单点儿就是一个代理方案。</p>
<p>举个例子，当我们调用本地的一个服务类的某个方法时，实际上这个服务类已经是一个代理类了，调用这个类的某个方法，实际上代理方法中可以加入很多额外的逻辑，比如构造 HTTP 请求、构造TCP 请求等等，最终穿过网络调用到真正的服务提供方的同名方法。</p>
<p>说到 Java 中的代理技术，相信大家都不陌生，有动态代理、静态代理，不了解的同学可以参考 <a href="https://mp.weixin.qq.com/s?__biz=MzAxMjA0MDk2OA==&mid=2449468644&idx=1&sn=55899dfeadca950bf6033c3e5828d9db&chksm=8fbca283b8cb2b95ee7241fededea8942258deaa085931f646addb1f214bd7b730693dd8135e&token=1613720944&lang=zh_CN#rd">Spring AOP 和 动态代理技术</a> 这篇文章。</p>
<h2 id="协议的实现">协议的实现</h2>
<p>这里说的协议就是网络通信协议。远程过程调用嘛，那必须得通过网络传输才行，而通过网络传输那就得有遵行规定的协议。</p>
<p>这个协议是用来规范传输的数据的，所以它是一个应用层协议，比如 HTTP ，而不是传输层的协议，比如 TCP 、UDP 。</p>
<p>比如 GRPC 框架中用的协议是 HTTP2，Dubbo中可以选择多种协议，比如Hession、HTTP等。</p>
<p>下图是 HTTP 1.0 协议的格式，包括首部和请求内容，首部可以指定请求方式、URI、版本、内容类型和长度等，内容部分就是真正要传输的数据， 如果是一个 RPC 调用的话，那就是调用方法的参数和一些额外的信息（比如调用的方法名、类名等），首部和内容由一个空行分隔。</p>
<p>接收端可以根据空行了解哪些是头部，哪些是内容。</p>
<p>可以通过首部的内容长度字段来进行分配内存或其他操作。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230708192233439.png"></p>
<p>其他的协议都有自己的格式，我们也可以自定义一个协议。如下是一个简单的协议格式：</p>
<p>通过魔术位可以直接判断是不是本协议数据，如果是的话，再进行处理。</p>
<p>通过整体长度和首部长度可以确定数据内容的长度，用来解析首部和数据。</p>
<p>通过序列化方式字段可以确定数据序列化采用的什么方式，用来实现反序列化。</p>
<table>
<thead>
<tr>
<th>0-8bit</th>
<th>9-24bit</th>
<th>25-32bit</th>
<th>33-40bit</th>
<th>不定长</th>
</tr>
</thead>
<tbody>
<tr>
<td>标志位（魔术位）</td>
<td>整体长度</td>
<td>首部长度</td>
<td>序列化方式</td>
<td>数据内容</td>
</tr>
</tbody>
</table>
<p>一个 RPC 框架使用哪种协议是有权衡取舍的，如果考虑通用性那就可能使用 HTTP或者 HTTP2这种协议，比如 GRPC ，这样一来，可以在各种语言框架中通用，但是性能就稍微差一点了。</p>
<p>而如果从性能方面考虑呢，那就要牺牲掉一部分通用性了，比如Hession、Dubbo2协议，只有框架开发者提供了对应语言的版本，才能在相应的语言中使用。</p>
<h3 id="序列化和反序列化编解码">序列化和反序列化（编解码）</h3>
<p>上面说的协议主要指的是数据通信协议，而序列化和反序列化其实也要遵循一定的协议。</p>
<p>说到序列化和反序列化，其实我们并不陌生。在Java开发中，经常会遇到。最常用的就是把 Java Bean 序列化为 JSON，将 JSON 反序列化为 Java Bean。</p>
<p>JSON 就是最常见的一种序列化和发序列化协议，其他的序列化方式还有 XML、GRPC 中用到的 Protocol Buffers。 Hessian 也有它自己的序列化算法。</p>
<p>为什么要有序列化呢，数据要想在网络中传输，那必须是二进制的形式。在远程调用里，我们像调用本地方法那样调用，使用的参数都是和当前项目语言一致的参数类型，比如Java中的 <code>String</code>、<code>List</code>、<code>Map</code>等等，那要把这些类型转换成二进制，靠的就是序列化算法。</p>
<p>JSON 和 XML 这种文本序列化方式比较简单，使用场景非常广泛，通用性强，但是序列化后产生的二进制体积也比较大，这样在传输的时候就会比较耗时、占用带宽。</p>
<p>而像 Protocol Buffers 这种序列化方式，它使用结构化的消息定义语言（IDL）来定义数据结构和服务接口，支持多种编程语言，而且序列化后的二进制也会最大程度的压缩，少占用带宽，有很高的传输效率。</p>
<h2 id="总结">总结</h2>
<p>下图是整个 RPC 调用过程的简化版。</p>
<p><img src="https://hexo.moonkite.cn/blog/%E8%AF%A6%E7%BB%86%E6%B5%81%E7%A8%8B%E5%9B%BE.png"></p>
<p>序列化和反序列化功能会在代理中进行，在很多框架中也称这部分为编解码。</p>
<p>封装的网络请求既有可能是 HTTP 请求，也有可能是 Sockets 请求，比如有很多 RPC 功能都使用 Netty 作为通讯层。</p>
<p>除了主要的功能外，一个成熟的 RPC 框架还有考虑诸多其他因素，比如性能、安全性、兼容性等等。</p>
]]></content:encoded></item><item><title>0. Spring Cloud 是什么</title><link>https://www.showcodeba.com/category/SpringCloud/0-Srping-Cloud-%E5%BC%80%E7%AF%87/</link><pubDate>Fri, 02 Jun 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/SpringCloud/0-Srping-Cloud-%E5%BC%80%E7%AF%87/</guid><description>本篇是我的 Spring Cloud 系列的开篇，那么多人都在写 Spring Cloud 的相关文章，为啥我还要写呢? 答：由于本人才疏学浅，学点东西就爱做个笔记，也不奢望多少人看到，主要</description><content:encoded><![CDATA[<p><em>本篇是我的 Spring Cloud 系列的开篇，那么多人都在写 Spring Cloud 的相关文章，为啥我还要写呢?</em></p>
<p>答：由于本人才疏学浅，学点东西就爱做个笔记，也不奢望多少人看到，主要是怕自己日后忘记，当然如果有人看到并且学到点东西，我也是非常高兴的。</p>
<p>Spring Cloud 有众多模块，一旦项目搭起来，有些模块可能就不怎么动了，例如网关、服务注册中心等，知识点就是这样，一旦长时间不用，慢慢印象也就模糊了。</p>
<p><em>那么多的 Spring Cloud 的文章，我写的有啥不同吗？</em></p>
<p>答：没有不同。无论是什么框架，说白了也就是个工具，工具的使用都是一样的，所以使用手册也是大同小异。当然，使用姿势正确得当，系统也就更稳定，性能更好。我这个系列也是介绍 Spring Cloud 各模块的使用。所以说，看其他的文章和看我写的也都差不多。</p>
<p>以上。</p>
<p><em>以下，正文开始</em></p>
<p>说到 Spring Cloud，那肯定要少不了提一下微服务框架，所谓的微服务框架就是把负责的功能拆分成比较小、功能比较单一的服务独立处理，例如单点登录服务、支付服务、订单服务等，当然如果订单功能比较复杂还可以独立出更具体的服务。</p>
<p>我在之前的文章中（https://www.cnblogs.com/fengzheng/p/5847441.html）也提到过微服务的几个主要特点，个人感觉：</p>
<p>1、微服务在架构设计方面：可以帮助我们理清业务，更加清晰的设计系统架构。</p>
<p>2、微服务在开发方面：可以降低项目结构的复杂度，降低代码内部的耦合度。</p>
<p>3、在运维方面：可以清晰快速的定位排查问题。</p>
<p>微服务框架里 Dubbo 和 Spring Cloud 这两个用的最多。随着 docker 和 容器编排技术的成熟，最近用 Docker、Kubernetes 构建微服务架构的也越来越多了。</p>
<p>公司之前的项目中有一部分采用 Spring MVC 方式开发，所以一直在用 Dubbo。新的系统全部采用 Spring Boot，顺便也就使用了 Spring Cloud 技术栈。我倒不觉得两者有什么高下之分，只要能够满足业务需求，使用哪种技术倒无所谓，况且 Dubbo 用下来也没什么问题。</p>
<p>今天就是说一下 Spring Cloud，Spring Cloud 就是一个大集合，包括了配置中心、注册中心、负责均衡、熔断处理等等一些成熟的框架，并用 Spring boot 的形式包装好，方便开发者使用。Spring boot 相信大家肯定都不陌生，应该做 Java 开发的几乎或多或少都使用过。它比起 Spring MVC 来，省去了繁杂的配置文件，可谓是开发微服务项目的不二选择。</p>
<p>Spring Cloud 包括众多的子项目，可谓是微服务行业一条龙服务。只有你想不到，没有你找不到的。</p>
<h2 id="spring-cloud-config">Spring Cloud Config</h2>
<p>配置中心，将系统中用到的一些配置信息存储到配置中心，方便维护，不用每次修改配置都重启服务。用的比较多的配置中心还有etcd、携程的 Apollo、Disconf 。</p>
<h2 id="spring-cloud-netflix">Spring Cloud Netflix</h2>
<p>Netflix OSS 是一组开源的框架和组件库，是Netflix公司开发出来解决分布式系统的一些有趣的可扩展类库。Spring Cloud 把他们都放到 Spring Cloud Netflix 下，这是一个框架集合，它包括 Eureka 、Ribbon、Zuul、Hystrix 等。</p>
<p><strong>Eureka</strong></p>
<p>服务中心，这可以说是微服务架构的核心功能了，微服务部署之后，一定要有服务注册和发现的能力，Eureka 就是担任这个角色的。如果你用过 dubbo 的话，那一定知道 dubbo 中服务注册和发现的功能是用 zookeeper 来实现的。</p>
<p>目前官方已停止升级，如果你的项目中还没有使用并且打算使用注册中心的话，可以直接使用 Spring Cloud Consul。</p>
<p><strong>Ribbon</strong></p>
<p>提供客户端负责均衡功能，例如一个服务提供者部署了 3 个实例，那么使用 Ribbon 可以指定负载均衡算法请求其中一个实例，Ribbon 如果配合 Eureka ，使用起来非常简单。</p>
<p><strong>Hystrix</strong></p>
<p>熔断器，假设有 3 个服务提供实例，其中有一个实例由于某中原因挂掉了，那么当再有请求进来的时候，如果还是向这个实例上发请求，那将会导致请求积压阻塞，这个时候，熔断器就要发挥它的作用，将这个有问题的实例下线，这样一来，再有新的请求进来，就不会再发到这个有问题的实例上了。</p>
<p><strong>Zuul</strong></p>
<p>服务网关。主要实现了路由转发和过滤器功能，对于处理一些数据聚合、鉴权、监控、统计类的功能非常好用。</p>
<p><strong>Gateway</strong></p>
<p>也是服务网关，可以认为它是 Zuul 的下一代，无论从易用性和性能方便都有所提高，如果你的系统中还没有使用 Zuul ，并且准备上网关，可以直接选择 Gateway 。</p>
<h2 id="spring-cloud-consul">Spring Cloud Consul</h2>
<p>Consul 让服务注册和服务发现（通过 DNS 和 HTTP 接口）更加简单，甚至对于外部服务（例如SaaS）注册也一样。Spring Cloud Consul 可替代已有的 Spring Cloud Eureka。Eureka 2.x 版本也已经停止开发，并且 Spring Cloud 官方也建议用 Spring Cloud Consul 来替代，当然如果已经用了 Eureka 在项目中也没有关系，Eureka 已经足够稳定，正常使用没有任何问题。</p>
<p>Spring Cloud Consul 可替代已有的 Spring Cloud Config ，当做配置中心使用。</p>
<h2 id="spring-cloud-stream">Spring Cloud Stream</h2>
<p>Spring Cloud Stream 是消息中间件组件，它集成了 kafka 和 rabbitmq 。如果你的系统中打算集成 kafka 或 rabbitmq，可以考虑使用 Stream 。</p>
<h2 id="spring-cloud-bus">Spring Cloud Bus</h2>
<p>消息总线，用于在集群（例如，配置变化事件）中传播状态变化，可与Spring Cloud Config联合实现热部署。集成了 Rabbitmq 和 kafka 。刚刚上面说到的 Stream 好像也是这个功能。没错，我们可以认为 Bus 是 Stream 的进一步封装，使用更简单。而 Stream 的灵活性更高。</p>
<h2 id="spring-cloud-feign">Spring Cloud Feign</h2>
<p>Feign是一种声明式、模板化的HTTP客户端。它可以用注解的方式简化 HTTP 请求，可以快速简单的请求服务提供接口。如果你还在使用 restTemplate 或者其他方式，不妨考虑一下 Feign。</p>
<h2 id="spring-cloud-sleuth">Spring Cloud Sleuth</h2>
<p>服务日志收集和链路追踪模块，封装了 Dapper 和 log-based 追踪以及 Zipkin 和 HTrace 操作。与之功能相同的框架还有 skywalking、Pinpoint，另外国内还有美团开源的 CAT，只不过 CAT 属于代码侵入的，需要开发人员在系统中做代码埋点，不过也更加灵活，更加精细。</p>
<h2 id="spring-cloud-security">Spring Cloud Security</h2>
<p>可用做授权服务、单点登录等。如果服务需要做权限控制，那除非你自己实现。不然用到最多的就是 shiro  和 Spring Security 。Spring Boot 中用的比较多的就是 Security，众多授权机制中属于 OAuth2 最为流行。Spring Cloud Security 就是将 Security、OAuth2 做了集成，方便使用。</p>
<p>除了以上的这些常用功能外，Spring Cloud 还提供了一些其他的模块，具体的可以到 Spring Cloud 官网一看。官网上介绍的更加详细。官网地址:https://spring.io/projects/spring-cloud</p>
]]></content:encoded></item><item><title>1. Spring Cloud Eureka 实现服务注册与发现</title><link>https://www.showcodeba.com/category/SpringCloud/1-Spring-Cloud-Eureka-%E5%AE%9E%E7%8E%B0%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E4%B8%8E%E5%8F%91%E7%8E%B0/</link><pubDate>Fri, 02 Jun 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/SpringCloud/1-Spring-Cloud-Eureka-%E5%AE%9E%E7%8E%B0%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E4%B8%8E%E5%8F%91%E7%8E%B0/</guid><description>Eureka 是 Netflix 开源的服务注册发现组件，服务发现可以说是微服务架构的核心功能了，微服务部署之后，一定要有服务注册和发现的能力，Eureka 就是担任这个</description><content:encoded><![CDATA[<blockquote>
<p>Eureka 是 Netflix 开源的服务注册发现组件，服务发现可以说是微服务架构的核心功能了，微服务部署之后，一定要有服务注册和发现的能力，Eureka 就是担任这个角色的。如果你用过 dubbo 的话，那一定知道 dubbo 中服务注册和发现的功能是用 zookeeper 来实现的。</p>
</blockquote>
<p>Eureka 目前是 2.x 版本，并且官方已经宣布不再维护更新。不过其实 Eureka 已经很稳定了，当做注册中心完全没有问题。Spring Cloud 集成了 Eureka ，并做了完善的封装。方便我们使用 Spring boot 开发的时候简单配置就可以使用。</p>
<p>微服务框架中有三类角色，分别是注册中心、服务提供者、服务消费者，注册中心就是今天要说的主角 Eureka，这篇文章简单说明 Spring Cloud Eureka 的使用，模拟实现单点和高可用注册中心，并简单介绍服务提供者和服务消费者如何使用 Eureka 提供的服务注册和发现功能。</p>
<p><strong>版本说明</strong>
Java : 1.8</p>
<p>Spring Boot : 2.1.3.RELEASE</p>
<p>Spring Cloud: Finchley.SR2</p>
<p>之说以要说一下版本，因为 Finchley.SR2 版本较之前的版本包名有变化，所以在引用 maven 包的时候要注意。</p>
<h2 id="单点注册中心"><strong>单点注册中心</strong></h2>
<h5 id="创建-eureka-注册中心"><strong>创建 Eureka 注册中心</strong></h5>
<p>1、引用 maven 包，其中</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependencyManagement&gt;</span> 
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;dependencies&gt;</span> 
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;dependency&gt;</span> 
</span></span><span class="line"><span class="cl">      <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>  
</span></span><span class="line"><span class="cl">      <span class="nt">&lt;artifactId&gt;</span>spring-cloud-dependencies<span class="nt">&lt;/artifactId&gt;</span>  
</span></span><span class="line"><span class="cl">      <span class="nt">&lt;version&gt;</span>Finchley.SR2<span class="nt">&lt;/version&gt;</span>  
</span></span><span class="line"><span class="cl">      <span class="nt">&lt;type&gt;</span>pom<span class="nt">&lt;/type&gt;</span>  
</span></span><span class="line"><span class="cl">      <span class="nt">&lt;scope&gt;</span>import<span class="nt">&lt;/scope&gt;</span> 
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/dependency&gt;</span> 
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/dependencies&gt;</span> 
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependencyManagement&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&lt;!-- 最新版的 eureka 服务端包 --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span> 
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>  
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-netflix-eureka-server<span class="nt">&lt;/artifactId&gt;</span> 
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&lt;!-- 监控管理 --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span> 
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>  
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-actuator<span class="nt">&lt;/artifactId&gt;</span> 
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>2、新建 bootstrap.yml，并配置 Spring cloud 参数</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">kite-eureka-center</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">inetutils</span><span class="p">:</span><span class="w"> </span><span class="c">## 网卡设置</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ignoredInterfaces</span><span class="p">:</span><span class="w">  </span><span class="c">## 忽略的网卡</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">docker0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">veth.*</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">VM.*</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">preferredNetworks</span><span class="p">:</span><span class="w">  </span><span class="c">## 优先的网段</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="m">192.168</span><span class="w">
</span></span></span></code></pre></div><p>3、新建 application.yml ，并配置参数</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">3000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">eureka</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">instance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">eureka-center</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">appname</span><span class="p">:</span><span class="w"> </span><span class="l">注册中心</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">registerWithEureka</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span><span class="c"># 单点的时候设置为 false 禁止注册自身</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">fetchRegistry</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">serviceUrl</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">defaultZone</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:3000/eureka</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enableSelfPreservation</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">evictionIntervalTimerInMs</span><span class="p">:</span><span class="w"> </span><span class="m">4000</span><span class="w">
</span></span></span></code></pre></div><p>bootstrap.yml 和 application.yml 的区别：</p>
<ul>
<li>
<p>bootstrap.yml 在 application.yml 之前启动；</p>
</li>
<li>
<p>bootstrap.yml 配置 application 的 name、spring.cloud.config.server.git.uri、一些encryption/decryption（加密/解密）信息；</p>
</li>
<li>
<p>application.yml 的信息会覆盖 bootstrap.yml 中的内容，当遇到相同的配置的时候；</p>
</li>
</ul>
<p>4、新建 Application.java 启动文件</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@EnableEurekaServer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@SpringBootApplication</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">Application</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">SpringApplication</span><span class="p">.</span><span class="na">run</span><span class="p">(</span><span class="n">Application</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p><code>@EnableEurekaServer</code> 表示使用 Eureka Server 端功能，也就是启动为一个注册中心节点。</p>
<p>5、运行 Application.java ，访问 http://localhost:3000 即可看到 Eureka 提供的 ui 控制台。</p>
<p><img alt="eureka-center" src="https://hexo.moonkite.cn/blog/eureka-center.png"></p>
<h5 id="创建一个服务提供者"><strong>创建一个服务提供者</strong></h5>
<p>接下来创建一个服务提供者，并注册到上面创建的 Eureka 注册中心。</p>
<p>1、添加 maven 依赖包</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-web<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c">&lt;!-- eureka 客户端 --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-netflix-eureka-client<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>2、配置 application.yml</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">3001</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">eureka</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">instance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">preferIpAddress</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">serviceUrl</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">defaultZone</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:3000/eureka </span><span class="w"> </span><span class="c">## 注册到 eureka </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">single-provider </span><span class="w"> </span><span class="c">## 应用程序名称，后面会在消费者中用到</span><span class="w">
</span></span></span></code></pre></div><p>3、创建一个简单的 RESTful 接口 controller</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Slf4j</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@RestController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">ProviderController</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">DiscoveryClient</span><span class="w"> </span><span class="n">discoveryClient</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@RequestMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;/hello&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">hello</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">List</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">services</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">discoveryClient</span><span class="p">.</span><span class="na">getServices</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="n">services</span><span class="p">){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="n">s</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;hello spring cloud!&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@RequestMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;/nice&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">nice</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">List</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">services</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">discoveryClient</span><span class="p">.</span><span class="na">getServices</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="n">services</span><span class="p">){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">&#34;gogogo&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">s</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;nice to meet you!&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>4、创建 spring boot 启动类</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@EnableEurekaClient</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@SpringBootApplication</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">SingleProviderApplication</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">SpringApplication</span><span class="p">.</span><span class="na">run</span><span class="p">(</span><span class="n">SingleProviderApplication</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p><code>@EnableEurekaClient</code> 修饰，表示要注册到注册中心。</p>
<p>5、启动项目，正常情况下就注册到了 Eureka 注册中心，打开 Eureka 控制台，会看到已经出现了这个服务</p>
<p><img alt="eureka-single-provider" src="/Users/fengzheng/%E7%9F%A5%E8%AF%86%E7%AE%A1%E7%90%86/%E6%8A%80%E6%9C%AF%E5%86%99%E4%BD%9C/Spring-Cloud/images/eureka-single-provider.png"></p>
<h5 id="创建一个服务消费者"><strong>创建一个服务消费者</strong></h5>
<p>有了服务提供者，接下来创建一个消费者来消费一下</p>
<p>1、引用 maven 包</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-netflix-eureka-client<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-web<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-openfeign<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-actuator<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>2、配置 application.yml</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">3002</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">eureka</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">serviceUrl</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">defaultZone</span><span class="p">:</span><span class="w"> </span><span class="l">http://127.0.0.1:3000/eureka </span><span class="w"> </span><span class="c">## 注册到 eureka</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">instance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">preferIpAddress</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">single-customer  </span><span class="w">
</span></span></span></code></pre></div><p>3、开始消费提供者提供的服务接口，这里演示了两种消费方法，一种是用 RestTemplate ，另外一种是用 FeignClient，Feign 同样是 Netflix 开源，并被 Spring Cloud 封装到 spring-cloud-starter-openfeign 包中。</p>
<p>创建启动类，并添加相关注解</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@SpringBootApplication</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableEurekaClient</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableFeignClients</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">SingleCustomerApplication</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 注入 RestTemplate 
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 并用 @LoadBalanced 注解，用负载均衡策略请求服务提供者
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 这是 Spring Ribbon 的提供的能力
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @return
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@LoadBalanced</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Bean</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">RestTemplate</span><span class="w"> </span><span class="nf">restTemplate</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">RestTemplate</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">SpringApplication</span><span class="p">.</span><span class="na">run</span><span class="p">(</span><span class="n">SingleCustomerApplication</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p><code>@EnableEurekaClient</code> 声明此项目为一个 eureka 客户端，<code>@EnableFeignClients</code> 声明此项目可以使用 Feign。</p>
<p>4、创建一个服务接口类，这是 Feign 的使用方式，详细的用法可以查一下 Spring Cloud Feign 相关文档</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * IHelloService
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 配置服务提供者：single-provider 是服务提供者的 application.name
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@FeignClient</span><span class="p">(</span><span class="s">&#34;single-provider&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">interface</span> <span class="nc">IHelloService</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@RequestMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;/hello&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="nf">hello</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@RequestMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;nice&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="nf">nice</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p><code>@FeignClient</code> 注解的 value 为服务提供者的 appplication.name 。</p>
<p>5、创建一个 Controller 用于调用服务</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@RestController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">ConsumerController</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">RestTemplate</span><span class="w"> </span><span class="n">restTemplate</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">IHelloService</span><span class="w"> </span><span class="n">helloService</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">applicationName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;single-provider&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@RequestMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;feignRequest&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">feignRequest</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">helloService</span><span class="p">.</span><span class="na">nice</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">s</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@RequestMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;commonRequest&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">commonRequest</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">url</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;http://&#34;</span><span class="o">+</span><span class="w"> </span><span class="n">applicationName</span><span class="w"> </span><span class="o">+</span><span class="s">&#34;/hello&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">restTemplate</span><span class="p">.</span><span class="na">getForObject</span><span class="p">(</span><span class="n">url</span><span class="p">,</span><span class="n">String</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">s</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>其中 feignRequest 方法是使用了 Feign 的方式调用服务接口；</p>
<p>commonRequest 方法是用 RestTemplate 提供的方法调用服务接口；</p>
<p>6、最后，启动服务，访问地址：http://localhost:3002/commonRequest 和 http://localhost:3002/feignRequest</p>
]]></content:encoded></item><item><title>2. Spring Cloud Eureka 实现安全控制</title><link>https://www.showcodeba.com/category/SpringCloud/2-Spring-Cloud-Eureka-%E5%AE%9E%E7%8E%B0%E5%AE%89%E5%85%A8%E6%8E%A7%E5%88%B6/</link><pubDate>Fri, 02 Jun 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/SpringCloud/2-Spring-Cloud-Eureka-%E5%AE%9E%E7%8E%B0%E5%AE%89%E5%85%A8%E6%8E%A7%E5%88%B6/</guid><description>上一篇「Eureka 实现微服务注册发现」讲了用 Eureka 实现单体版的服务注册与发现。因为本篇是在上一篇的基础上的一点扩充，所以读此篇之前要保证看了上</description><content:encoded><![CDATA[<p>上一篇「Eureka 实现微服务注册发现」讲了用 Eureka 实现单体版的服务注册与发现。因为本篇是在上一篇的基础上的一点扩充，所以读此篇之前要保证看了上一篇。</p>
<p><strong>Eureka 如果不加安全控制，会存在下列问题</strong>：</p>
<blockquote>
<ol>
<li>Eureka 控制台可以看到各服务的状态和参数等信息，如果不加控制，只要知道注册中心的地址，就可以登录上去看到各服务信息；</li>
<li>只要知道注册中心地址，服务提供者就可以注册上来，对外提供服务；</li>
<li>只要知道注册中心地址，服务消费者就可以发现注册中心的服务，并调用服务；</li>
</ol>
</blockquote>
<p>为了安全起见，我们还是为 Eureka 增加安全控制，这里用 Spring Security 实现最基础的用户名、密码控制。</p>
<p>一个小知识点而已，几个配置就可以完成。</p>
<p><em>1. maven 包引用</em></p>
<p>在服务注册中心项目的 pom.xml 文件中引入 Spring Security 包。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-security<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p><em>2.增加应用配置</em></p>
<p>在应用配置文件中增加关于 security 的配置，一般是放在 application.yml ，但是本项目中分了 application.yml 和 bootstrap.yml 两个配置文件，所以我这里是放在了 application.yml 中。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">kite-eureka-center</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">security</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">user</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">test  			</span><span class="w"> </span><span class="c"># 用户名</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="m">123456</span><span class="w">   </span><span class="c"># 密码</span><span class="w">
</span></span></span></code></pre></div><p>此时，启动并访问 Eureka 管理控制台，会提示输入用户名和密码，输入上面的 name 和 password 即可。</p>
<p><em>3.服务提供者注册服务</em></p>
<p>在服务提供者的应用配置文件中做以下修改即可</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">eureka</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">instance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">statusPageUrlPath</span><span class="p">:</span><span class="w"> </span><span class="l">/actuator/info</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">healthCheckUrlPath</span><span class="p">:</span><span class="w"> </span><span class="l">/actuator/health</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">prefer-ip-address</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">register-with-eureka</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">fetch-registry</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">service-url</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">defaultZone</span><span class="p">:</span><span class="w"> </span><span class="l">http://test:123456@localhost:3000/eureka/</span><span class="w">
</span></span></span></code></pre></div><p>上述配置与没有安全控制的时候唯一的差别就是在 defaultZone 指定的 eureka 地址。在地址中增加了用户名和密码。
<strong>没有安全控制之前的地址为</strong>：</p>
<pre tabindex="0"><code>http://localhost:3000/eureka/
</code></pre><p><strong>加了安全控制之后的地址为</strong>：</p>
<pre tabindex="0"><code>http://test:123456@localhost:3000/eureka/
</code></pre><p><em>4.服务消费者</em></p>
<p>服务消费者和服务提供者配置方式一致。</p>
<p>具体的代码已经放到 github。<a href="https://github.com/huzhicheng/spring-cloud-study/tree/master/eureka">源码地址</a></p>
]]></content:encoded></item><item><title>3. Spring Cloud Eureka 实现高可用服务发现注册中心</title><link>https://www.showcodeba.com/category/SpringCloud/3-Spring-Cloud-Eureka-%E5%AE%9E%E7%8E%B0%E9%AB%98%E5%8F%AF%E7%94%A8%E6%9C%8D%E5%8A%A1%E5%8F%91%E7%8E%B0%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83/</link><pubDate>Fri, 02 Jun 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/SpringCloud/3-Spring-Cloud-Eureka-%E5%AE%9E%E7%8E%B0%E9%AB%98%E5%8F%AF%E7%94%A8%E6%9C%8D%E5%8A%A1%E5%8F%91%E7%8E%B0%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83/</guid><description>前两篇实现了单点注册中心的实现，并实现了一个服务提供者和一个消费者，之后在基础上有增加了安全控制。 本篇来实现一个高可用版的服务注册中心。在线</description><content:encoded><![CDATA[<p>前两篇实现了单点注册中心的实现，并实现了一个服务提供者和一个消费者，之后在基础上有增加了安全控制。</p>
<p>本篇来实现一个高可用版的服务注册中心。在线上环境中，使用高可用是最基本的要求，如果有单点故障的时候，不至于整个服务都无响应。</p>
<p>这里的高可用包括注册中心的高可用，服务提供者注册到服务中心集群上，这样服务消费者发现服务的时候就通过集群来实现，不会出现注册中心不可用，造成整个系统崩溃。</p>
<p>还包括服务提供者的高可用，一个服务提供者注册多个实例到注册中心，这样，即时有其中某个实例 down 掉，其他实例依然可以提供服务。</p>
<h2 id="高可用服务注册发现中心">高可用服务注册发现中心</h2>
<p>下图是 Eureka 官方提供的高可用架构图。</p>
<p><img alt="eureka_architecture" src="https://hexo.moonkite.cn/blog/eureka_architecture.png"></p>
<p>1、创建注册中心，重点在于配置文件</p>
<p>bootstrap.yml 配置如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">eureka-ha-center </span><span class="w"> </span><span class="c">## 设置应用的名称</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">inetutils</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ignoredInterfaces</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">docker0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">veth.*</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">VM.*</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">preferredNetworks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="m">192.168</span><span class="w">
</span></span></span></code></pre></div><p>application.yml 配置如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">profiles</span><span class="p">:</span><span class="w"> </span><span class="l">eureka-center1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">1989</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">eureka</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">instance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">ha-eureak-center1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">appname</span><span class="p">:</span><span class="w"> </span><span class="l">注册中心</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">registerWithEureka</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">fetchRegistry</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">serviceUrl</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">defaultZone</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:1988/eureka</span><span class="w"> </span><span class="c">## 注册到 eureka-center2 上，端口1988</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">profiles</span><span class="p">:</span><span class="w"> </span><span class="l">eureka-center2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">1988</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">eureka</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">instance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">ha-eureak-center2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">appname</span><span class="p">:</span><span class="w"> </span><span class="l">注册中心</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">registerWithEureka</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">fetchRegistry</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">serviceUrl</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">defaultZone</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:1989/eureka </span><span class="w"> </span><span class="c">## 注册到 eureka-center1 上，端口1989</span><span class="w">
</span></span></span></code></pre></div><p>通过 profiles 参数，稍后在启动的时候根据 vm 参数决定启用配置。</p>
<p>client.registerWithEureka 和 client.registerWithEureka 设置为 true，表示要注册到 eureka 。单点模式中设置为 false。高可用版本要允许注册到 eureka 。注意：eureka-center1 和 eureka-center2 的 serviceUrl.defaultZone 是互相注册的。</p>
<p>2、启动应用。</p>
<p>*-*通过设置 vm 参数 -Dspring.profiles.active=eureka-center1 在 1989 端口启动注册中心1</p>
<p>*-*通过设置 vm 参数 -Dspring.profiles.active=eureka-center2 在 1988 端口启动注册中心2。</p>
<p>之后访问 http://localhost:1988 或 http://localhost:1989 都能进入 eureka ui 界面，并且能看到注册到自身的eureka 服务。</p>
<p><img alt="eureka-ha-center" src="https://hexo.moonkite.cn/blog/eureka-ha-center.png"></p>
<p>2、创建高可用的服务提供者，并注册到上面的两个注册中心</p>
<p>application.yml 配置如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">profiles</span><span class="p">:</span><span class="w"> </span><span class="l">ha-provider1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">ha-provider</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">security</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">user</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">root</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">root</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">1990</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">eureka</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">instance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">preferIpAddress</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">serviceUrl</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">defaultZone</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:1989/eureka,http://localhost:1988/eureka</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">profiles</span><span class="p">:</span><span class="w"> </span><span class="l">ha-provider2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">ha-provider</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">security</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">user</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">root</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">root</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">1991</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">eureka</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">instance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">preferIpAddress</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">serviceUrl</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">defaultZone</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:1989/eureka,http://localhost:1988/eureka</span><span class="w">
</span></span></span></code></pre></div><p>同样利用 profiles 区分两个服务提供者。</p>
<p>注意，defaultZone 设置了上面启动的两个注册中心服务，中间用逗号隔开。</p>
<p>之后设置 vm 参数 -Dspring.profiles.active=ha-provider1 启动第一个服务提供者，设置 vm 参数 -Dspring.profiles.active=ha-provider2 启动第二个服务提供者。</p>
<p>之后打开 eureka ui 界面，看到已经注册到注册中心</p>
<p><img alt="eurea-ha-provider" src="https://hexo.moonkite.cn/blog/eurea-ha-provider.png"></p>
<p>3、创建服务消费者</p>
<p>application.yml 配置：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">ha-customer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">1992</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">eureka</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">serviceUrl</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">defaultZone</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:1989/eureka,http://localhost:1988/eureka</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">instance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">preferIpAddress</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div><p>启动服务消费者</p>
<p>通过访问 http 接口，查看服务是否可用，通过日志可以看出每次调用有可能负载到不同的服务提供者实例上。</p>
<p>4、停掉一个服务提供者。</p>
<p><img alt="eureka-ha-provider-down" src="https://hexo.moonkite.cn/blog/eureka-ha-provider-down.png"></p>
<p>访问 http 接口，不影响服务，会自动负载到正常的服务提供者实例上。</p>
<p>5、停掉一个注册中心实例。</p>
<p><img alt="eureka-ha-center-down" src="https://hexo.moonkite.cn/blog/eureka-ha-center-down.png"></p>
<p>访问 http 接口，仍然不受影响。</p>
]]></content:encoded></item><item><title>4. Spring Cloud Config 实现配置中心，看这一篇就够了</title><link>https://www.showcodeba.com/category/SpringCloud/4.-Spring-Cloud-Config-%E5%AE%9E%E7%8E%B0%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83/</link><pubDate>Fri, 02 Jun 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/SpringCloud/4.-Spring-Cloud-Config-%E5%AE%9E%E7%8E%B0%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83/</guid><description>Spring Cloud Config 是 Spring Cloud 家族中最早的配置中心，虽然后来又发布了 Consul 可以代替配置中心功能，但是 Config 依然适用于 Spring Cloud 项目，通过简单的配置即可实现功能。 配置文件是我</description><content:encoded><![CDATA[<blockquote>
<p>Spring Cloud Config 是 Spring Cloud 家族中最早的配置中心，虽然后来又发布了 Consul 可以代替配置中心功能，但是 Config 依然适用于 Spring Cloud 项目，通过简单的配置即可实现功能。</p>
</blockquote>
<p>配置文件是我们再熟悉不过的了，尤其是 Spring Boot 项目，除了引入相应的 maven 包之外，剩下的工作就是完善配置文件了，例如 mysql、redis 、security 相关的配置。除了项目运行的基础配置之外，还有一些配置是与我们业务有关系的，比如说七牛存储、短信相关、邮件相关，或者一些业务上的开关。</p>
<p>对于一些简单的项目来说，我们一般都是直接把相关配置放在单独的配置文件中，以 properties 或者 yml 的格式出现，更省事儿的方式是直接放到 application.properties 或 application.yml 中。但是这样的方式有个明显的问题，那就是，当修改了配置之后，必须重启服务，否则配置无法生效。</p>
<p>目前有一些用的比较多的开源的配置中心，比如携程的 Apollo、蚂蚁金服的 disconf 等，对比 Spring Cloud Config，这些配置中心功能更加强大。有兴趣的可以拿来试一试。</p>
<p><strong>接下来</strong>，我们开始在 Spring Boot 项目中集成 Spring Cloud Config，并以 github 作为配置存储。除了 git 外，还可以用数据库、svn、本地文件等作为存储。主要从以下三块来说一下 Config 的使用。</p>
<p>*1.*基础版的配置中心（不集成 Eureka）;</p>
<p>*2.*结合 Eureka 版的配置中心;</p>
<p>*3.*实现配置的自动刷新；</p>
<h2 id="实现最简单的配置中心">实现最简单的配置中心</h2>
<p>最简单的配置中心，就是启动一个服务作为服务方，之后各个需要获取配置的服务作为客户端来这个服务方获取配置。</p>
<p><em>先在 github 中建立配置文件</em></p>
<p>我创建的仓库地址为：<a href="https://github.com/huzhicheng/config-only-a-demo/tree/master">配置中心仓库</a></p>
<p>目录结构如下：</p>
<p><img src="https://hexo.moonkite.cn/blog/273364-20190725084302799-353615966.png"></p>
<p>配置文件的内容大致如下，用于区分，略有不同。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">env</span><span class="p">:</span><span class="w"> </span><span class="l">config-eureka-dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">user</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">eureka-client-user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="m">1291029102</span><span class="w">
</span></span></span></code></pre></div><p>注意文件的名称不是乱起的，例如上面的 config-single-client-dev.yml 和 config-single-client-prod.yml 这两个是同一个项目的不同版本，项目名称为 config-single-client， 一个对应开发版，一个对应正式版。config-eureka-client-dev.yml 和 config-eureka-client-prod.yml 则是另外一个项目的，项目的名称就是 config-eureka-client 。</p>
<p><em>创建配置中心服务端</em></p>
<p>1、新建 Spring Boot 项目，引入 config-server 和 starter-web</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-web<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&lt;!-- spring cloud config 服务端包 --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-config-server<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>2、配置 config 相关的配置项</p>
<p>bootstrap.yml 文件</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">config-single-server </span><span class="w"> </span><span class="c"># 应用名称</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="nt">config</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">git</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">uri</span><span class="p">:</span><span class="w"> </span><span class="l">https://github.com/huzhicheng/config-only-a-demo</span><span class="w"> </span><span class="c">#配置文件所在仓库</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">github 登录账号</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">github 登录密码</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">default-label</span><span class="p">:</span><span class="w"> </span><span class="l">master</span><span class="w"> </span><span class="c">#配置文件分支</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">search-paths</span><span class="p">:</span><span class="w"> </span><span class="l">config </span><span class="w"> </span><span class="c">#配置文件所在根目录</span><span class="w">
</span></span></span></code></pre></div><p>application.yml</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">3301</span><span class="w">
</span></span></span></code></pre></div><p>3、在 Application 启动类上增加相关注解 <code>@EnableConfigServer</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@SpringBootApplication</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableConfigServer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">Application</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">SpringApplication</span><span class="p">.</span><span class="na">run</span><span class="p">(</span><span class="n">Application</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>启动服务，接下来测试一下。</p>
<p>Spring Cloud Config 有它的一套访问规则，我们通过这套规则在浏览器上直接访问就可以。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="l">/{application}/{profile}[/{label}]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">/{application}-{profile}.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">/{label}/{application}-{profile}.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">/{application}-{profile}.properties</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">/{label}/{application}-{profile}.properties</span><span class="w">
</span></span></span></code></pre></div><p>{application} 就是应用名称，对应到配置文件上来，就是配置文件的名称部分，例如我上面创建的配置文件。</p>
<p>{profile} 就是配置文件的版本，我们的项目有开发版本、测试环境版本、生产环境版本，对应到配置文件上来就是以 application-{profile}.yml 加以区分，例如application-dev.yml、application-sit.yml、application-prod.yml。</p>
<p>{label} 表示 git 分支，默认是 master 分支，如果项目是以分支做区分也是可以的，那就可以通过不同的 label 来控制访问不同的配置文件了。</p>
<p>上面的 5 条规则中，我们只看前三条，因为我这里的配置文件都是 yml 格式的。根据这三条规则，我们可以通过以下地址查看配置文件内容:</p>
<p>http://localhost:3301/config-single-client/dev/master</p>
<p>http://localhost:3301/config-single-client/prod</p>
<p>http://localhost:3301/config-single-client-dev.yml</p>
<p>http://localhost:3301/config-single-client-prod.yml</p>
<p>http://localhost:3301/master/config-single-client-prod.yml</p>
<p>通过访问以上地址，如果可以正常返回数据，则说明配置中心服务端一切正常。</p>
<p><em>创建配置中心客户端，使用配置</em></p>
<p>配置中心服务端好了，配置数据准备好了，接下来，就要在我们的项目中使用它了。</p>
<p>1、引用相关的 maven 包。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-web<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&lt;!-- spring cloud config 客户端包 --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-config<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-actuator<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>2、初始化配置文件</p>
<p>bootstrap.yml</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">profiles</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">active</span><span class="p">:</span><span class="w"> </span><span class="l">dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">profiles</span><span class="p">:</span><span class="w"> </span><span class="l">prod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">config-single-client</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="nt">config</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="nt">uri</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:3301</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="nt">label</span><span class="p">:</span><span class="w"> </span><span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="nt">profile</span><span class="p">:</span><span class="w"> </span><span class="l">prod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">profiles</span><span class="p">:</span><span class="w"> </span><span class="l">dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">config-single-client</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="nt">config</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="nt">uri</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:3301</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="nt">label</span><span class="p">:</span><span class="w"> </span><span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="nt">profile</span><span class="p">:</span><span class="w"> </span><span class="l">dev</span><span class="w">
</span></span></span></code></pre></div><p>配置了两个版本的配置，并通过 spring.profiles.active 设置当前使用的版本，例如本例中使用的 dev 版本。</p>
<p>application.yml</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">3302</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">management</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">endpoint</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">shutdown</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">endpoints</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">web</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">exposure</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">include</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;*&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">env</span><span class="p">:</span><span class="w"> </span><span class="l">NaN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">user</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">NaN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">NaN</span><span class="w">
</span></span></span></code></pre></div><p>其中 management 是关于 actuator 相关的，接下来自动刷新配置的时候需要使用。</p>
<p>data 部分是当无法读取配置中心的配置时，使用此配置，以免项目无法启动。</p>
<p>3、要读取配置中心的内容，需要增加相关的配置类，Spring Cloud Config 读取配置中心内容的方式和读取本地配置文件中的配置是一模一样的。可以通过 @Value 或 @ConfigurationProperties 来获取。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@SpringBootApplication</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">Application</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">SpringApplication</span><span class="p">.</span><span class="na">run</span><span class="p">(</span><span class="n">Application</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>4、要读取配置中心的内容，需要增加相关的配置类，Spring Cloud Config 读取配置中心内容的方式和读取本地配置文件中的配置是一模一样的。可以通过 @Value 或 @ConfigurationProperties 来获取。</p>
<p>使用 @Value 的方式：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@Component</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">GitConfig</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Value</span><span class="p">(</span><span class="s">&#34;${data.env}&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">env</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Value</span><span class="p">(</span><span class="s">&#34;${data.user.username}&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">username</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Value</span><span class="p">(</span><span class="s">&#34;${data.user.password}&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">password</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>使用 @ConfigurationProperties 的方式：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Component</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@Data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@ConfigurationProperties</span><span class="p">(</span><span class="n">prefix</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;data&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">GitAutoRefreshConfig</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">class</span> <span class="nc">UserInfo</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">private</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">username</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">private</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">password</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">getUsername</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="n">username</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">setUsername</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">username</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">this</span><span class="p">.</span><span class="na">username</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">username</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">getPassword</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="n">password</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">setPassword</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">password</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">this</span><span class="p">.</span><span class="na">password</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">password</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">toString</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;UserInfo{&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;username=&#39;&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">username</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="sc">&#39;\&#39;&#39;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;, password=&#39;&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">password</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="sc">&#39;\&#39;&#39;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="sc">&#39;}&#39;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">env</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">UserInfo</span><span class="w"> </span><span class="n">user</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>4、增加一个 RESTController 来测试使用配置</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@RestController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">GitController</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">GitConfig</span><span class="w"> </span><span class="n">gitConfig</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">GitAutoRefreshConfig</span><span class="w"> </span><span class="n">gitAutoRefreshConfig</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;show&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">show</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">gitConfig</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;autoShow&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">autoShow</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">gitAutoRefreshConfig</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>5、项目启动类</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@SpringBootApplication</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">Application</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">SpringApplication</span><span class="p">.</span><span class="na">run</span><span class="p">(</span><span class="n">Application</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>启动项目，访问 RESTful 接口</p>
<p>http://localhost:3302/show，结果如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;env&#34;</span><span class="p">:</span> <span class="s2">&#34;localhost-dev-edit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;username&#34;</span><span class="p">:</span> <span class="s2">&#34;fengzheng-dev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;password&#34;</span><span class="p">:</span> <span class="s2">&#34;password-dev&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>http://localhost:3302/autoShow，结果如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;env&#34;</span><span class="p">:</span> <span class="s2">&#34;localhost-dev-edit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;user&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;username&#34;</span><span class="p">:</span> <span class="s2">&#34;fengzheng-dev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;password&#34;</span><span class="p">:</span> <span class="s2">&#34;password-dev&#34;</span>
</span></span><span class="line"><span class="cl">  	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="实现自动刷新">实现自动刷新</h2>
<p>Spring Cloud Config 在项目启动时加载配置内容这一机制，导致了它存在一个缺陷，修改配置文件内容后，不会自动刷新。例如我们上面的项目，当服务已经启动的时候，去修改 github 上的配置文件内容，这时候，再次刷新页面，对不起，还是旧的配置内容，新内容不会主动刷新过来。
但是，总不能每次修改了配置后重启服务吧。如果是那样的话，还是不要用它了为好，直接用本地配置文件岂不是更快。</p>
<p>它提供了一个刷新机制，但是需要我们主动触发。那就是 @RefreshScope 注解并结合 actuator ，注意要引入 spring-boot-starter-actuator 包。</p>
<p>1、在 config client 端配置中增加 actuator 配置，上面大家可能就注意到了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">management</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">endpoint</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">shutdown</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">endpoints</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">web</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">exposure</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">include</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;*&#34;</span><span class="w">
</span></span></span></code></pre></div><p>其实这里主要用到的是 refresh 这个接口</p>
<p>2、在需要读取配置的类上增加 @RefreshScope 注解，我们是 controller 中使用配置，所以加在 controller 中。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@RestController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@RefreshScope</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">GitController</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">GitConfig</span><span class="w"> </span><span class="n">gitConfig</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">GitAutoRefreshConfig</span><span class="w"> </span><span class="n">gitAutoRefreshConfig</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;show&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">show</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">gitConfig</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;autoShow&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">autoShow</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">gitAutoRefreshConfig</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>注意，以上都是在 client 端做的修改。</p>
<p>之后，重启 client 端，重启后，我们修改 github 上的配置文件内容，并提交更改，再次刷新页面，没有反应。没有问题。</p>
<p>接下来，我们发送 POST 请求到 http://localhost:3302/actuator/refresh 这个接口，用 postman 之类的工具即可，此接口就是用来触发加载新配置的，返回内容如下:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;config.client.version&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;data.env&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>之后，再次访问 RESTful 接口，http://localhost:3302/autoShow 这个接口获取的数据已经是最新的了，说明 refresh 机制起作用了。</p>
<p>而 http://localhost:3302/show 获取的还是旧数据，这与 @Value 注解的实现有关，所以，我们在项目中就不要使用这种方式加载配置了。</p>
<p><em>在 github 中配置 Webhook</em></p>
<p>这就结束了吗，并没有，总不能每次改了配置后，就用 postman 访问一下 refresh 接口吧，还是不够方便呀。 github 提供了一种 webhook 的方式，当有代码变更的时候，会调用我们设置的地址，来实现我们想达到的目的。</p>
<p>1、进入 github 仓库配置页面，选择 Webhooks ，并点击 add webhook；
<img src="https://hexo.moonkite.cn/blog/273364-20190725090559970-931500557.png"></p>
<p>2、之后填上回调的地址，也就是上面提到的 actuator/refresh 这个地址，但是必须保证这个地址是可以被 github 访问到的。如果是内网就没办法了。这也仅仅是个演示，一般公司内的项目都会有自己的代码管理工具，例如自建的 gitlab，gitlab 也有 webhook 的功能，这样就可以调用到内网的地址了。</p>
<p><img src="https://hexo.moonkite.cn/blog/273364-20190725090614229-390401707.png"></p>
<p><em>使用 Spring Cloud Bus 来自动刷新多个端</em></p>
<blockquote>
<p>Spring Cloud Bus 将分布式系统的节点与轻量级消息代理链接。这可以用于广播状态更改（例如配置更改）或其他管理指令。一个关键的想法是，Bus 就像一个扩展的 Spring Boot 应用程序的分布式执行器，但也可以用作应用程序之间的通信渠道。</p>
<p>—— Spring Cloud Bus 官方解释</p>
</blockquote>
<p>如果只有一个 client 端的话，那我们用 webhook ，设置手动刷新都不算太费事，但是如果端比较多的话呢，一个一个去手动刷新未免有点复杂。这样的话，我们可以借助 Spring Cloud Bus 的广播功能，让 client 端都订阅配置更新事件，当配置更新时，触发其中一个端的更新事件，Spring Cloud Bus 就把此事件广播到其他订阅端，以此来达到批量更新。</p>
<p>1、Spring Cloud Bus 核心原理其实就是利用消息队列做广播，所以要先有个消息队列，目前官方支持 RabbitMQ 和 kafka。</p>
<p>这里用的是 RabbitMQ， 所以先要搭一套 RabbitMQ 环境。请自行准备环境，这里不再赘述。我是用 docker 直接创建的，然后安装了 rabbitmq-management 插件，这样就可以在浏览器访问 15672 查看 UI 管理界面了。</p>
<p>2、在 client 端增加相关的包，注意，只在 client 端引入就可以。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-bus-amqp<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>3、在配置文件中增加 RabbitMQ 相关配置，默认的端口应该是 5672 ，因为我是用 docker 创建的，所以有所不同。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">rabbitmq</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">localhost</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">32775</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">guest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">guest</span><span class="w">
</span></span></span></code></pre></div><p>4、启动两个或多个 client 端，准备来做个测试</p>
<p>在启动的时候分别加上 vm option：-Dserver.port=3302 和 -Dserver.port=3303 ，然后分别启动就可以了。</p>
<p><img src="https://hexo.moonkite.cn/blog/273364-20190725090731099-791914304.png"></p>
<p>5、分别打开 http://localhost:3302/autoShow 和 http://localhost:3303/autoShow，查看内容，然后修改 github 上配置文件的内容并提交。再次访问这两个地址，数据没有变化。</p>
<p>6、访问其中一个的 actuator/bus-refresh 地址，注意还是要用 POST 方式访问。之后查看控制台输出，会看到这两个端都有一条这样的日志输出</p>
<pre tabindex="0"><code>o.s.cloud.bus.event.RefreshListener: Received remote refresh request. Keys refreshed
</code></pre><p>7、再次访问第 5 步的两个地址，会看到内容都已经更新为修改后的数据了。</p>
<p>综上所述，当我们修改配置后，使用 webhook ，或者手动触发的方式 POST 请求一个 client 端的 actuator/bus-refresh 接口，就可以更新给所有端了。</p>
<h2 id="结合-eureka-使用-spring-cloud-config">结合 Eureka 使用 Spring Cloud Config</h2>
<p>以上讲了 Spring Cloud Config 最基础的用法，但是如果我们的系统中使用了 Eureka 作为服务注册发现中心，那么 Spring Cloud Config 也应该注册到 Eureka 之上，方便其他服务消费者使用，并且可以注册多个配置中心服务端，以实现高可用。</p>
<p>好的，接下来就来集成 Spring Cloud Config 到 Eureka 上。</p>
<p><em>在 github 仓库中增加配置文件</em></p>
<p><img src="https://hexo.moonkite.cn/blog/273364-20190725090637487-1608990683.png"></p>
<p><em>启动 Eureka Server</em></p>
<p>首先启动一个 Eureka Server，之前的文章有讲过 Eureka ，可以回过头去看看。<a href="https://mp.weixin.qq.com/s/kGrWQP_n_RCYTTaHbWQ3xQ">Spring Cloud Eureka 实现服务注册发现</a>，为了清楚，这里还是把配置列出来</p>
<p>1、pom 中引入相关包</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-netflix-eureka-server<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-security<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>2、设置配置文件内容</p>
<p>bootstrap.yml</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">kite-eureka-center</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">security</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">user</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">test </span><span class="w"> </span><span class="c"># 用户名</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="m">123456</span><span class="w">   </span><span class="c"># 密码</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">inetutils</span><span class="p">:</span><span class="w"> </span><span class="c">## 网卡设置</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ignoredInterfaces</span><span class="p">:</span><span class="w">  </span><span class="c">## 忽略的网卡</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">docker0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">veth.*</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">VM.*</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">preferredNetworks</span><span class="p">:</span><span class="w">  </span><span class="c">## 优先的网段</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="m">192.168</span><span class="w">
</span></span></span></code></pre></div><p>application.yml</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">3000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">eureka</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">instance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">eureka-center</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">appname</span><span class="p">:</span><span class="w"> </span><span class="l">注册中心</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">registerWithEureka</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span><span class="c"># 单点的时候设置为 false 禁止注册自身</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">fetchRegistry</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">serviceUrl</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">defaultZone</span><span class="p">:</span><span class="w"> </span><span class="l">http://test:123456@localhost:3000/eureka</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enableSelfPreservation</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">evictionIntervalTimerInMs</span><span class="p">:</span><span class="w"> </span><span class="m">4000</span><span class="w">
</span></span></span></code></pre></div><p>3、Application 启动类</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@EnableEurekaServer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@SpringBootApplication</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">Application</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">SpringApplication</span><span class="p">.</span><span class="na">run</span><span class="p">(</span><span class="n">Application</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>4、启动服务，在浏览器访问 3000 端口，并输出用户名 test，密码 123456 即可进入 Eureka UI</p>
<p><em>配置 Spring Cloud Config 服务端</em></p>
<p>服务端和前面的相比也就是多了注册到 Eureka 的配置，其他地方都是一样的。</p>
<p>1、在 pom 中引入相关的包</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-web<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&lt;!-- spring cloud config 服务端包 --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-config-server<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&lt;!-- eureka client 端包 --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-netflix-eureka-client<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>2、配置文件做配置</p>
<p>application.yml</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">3012</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">eureka</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">serviceUrl</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">register-with-eureka</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">fetch-registry</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">defaultZone</span><span class="p">:</span><span class="w"> </span><span class="l">http://test:123456@localhost:3000/eureka/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">instance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">preferIpAddress</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">config-eureka-server</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="nt">config</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">git</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">uri</span><span class="p">:</span><span class="w"> </span><span class="l">https://github.com/huzhicheng/config-only-a-demo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">github 用户名</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">github 密码</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">default-label</span><span class="p">:</span><span class="w"> </span><span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">search-paths</span><span class="p">:</span><span class="w"> </span><span class="l">config</span><span class="w">
</span></span></span></code></pre></div><p>相比于不加 Eureka 的版本，这里仅仅是增加了 Eureka 的配置，将配置中心注册到 Eureka ，作为服务提供者对外提供服务。</p>
<p>3、启动类，在 @EnableConfigServer 的基础上增加了 @EnableEurekaClient，另外也可以用 @EnableDiscoveryClient 代替 @EnableEurekaClient</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@SpringBootApplication</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableConfigServer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableEurekaClient</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">Application</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">SpringApplication</span><span class="p">.</span><span class="na">run</span><span class="p">(</span><span class="n">Application</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>4、启动服务，之后访问 Eureka ，可以看到服务已注册成功</p>
<p><img src="https://hexo.moonkite.cn/blog/273364-20190725090702926-870149340.png"></p>
<p><em>配置 Spring Cloud Config 客户端</em></p>
<p>客户端的配置相对来说变动大一点，加入了 Eureka 之后，就不用再直接和配置中心服务端打交道了，要通过 Eureka 来访问。另外，还是要注意客户端的 application 名称要和 github 中配置文件的名称一致。</p>
<p>1、pom 中引入相关的包</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-web<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-config<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-actuator<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-netflix-eureka-client<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>2、配置文件</p>
<p>bootstrap.yml</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">eureka</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">serviceUrl</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">register-with-eureka</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">fetch-registry</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">defaultZone</span><span class="p">:</span><span class="w"> </span><span class="l">http://test:123456@localhost:3000/eureka/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">instance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">preferIpAddress</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">profiles</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">active</span><span class="p">:</span><span class="w"> </span><span class="l">dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">profiles</span><span class="p">:</span><span class="w"> </span><span class="l">prod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">config-eureka-client</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="nt">config</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="nt">label</span><span class="p">:</span><span class="w"> </span><span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="nt">profile</span><span class="p">:</span><span class="w"> </span><span class="l">prod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="nt">discovery</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="nt">service-id</span><span class="p">:</span><span class="w"> </span><span class="l">config-eureka-server</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">profiles</span><span class="p">:</span><span class="w"> </span><span class="l">dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">config-eureka-client</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="nt">config</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="nt">label</span><span class="p">:</span><span class="w"> </span><span class="l">master </span><span class="w"> </span><span class="c">#指定仓库分支</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="nt">profile</span><span class="p">:</span><span class="w"> </span><span class="l">dev  </span><span class="w"> </span><span class="c">#指定版本 本例中建立了dev 和 prod两个版本</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="nt">discovery</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">  </span><span class="c"># 开启</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">service-id</span><span class="p">:</span><span class="w"> </span><span class="l">config-eureka-server</span><span class="w"> </span><span class="c"># 指定配置中心服务端的server-id </span><span class="w">
</span></span></span></code></pre></div><p>除了注册到 Eureka 的配置外，就是配置和配置中心服务端建立关系。</p>
<p>其中 service-id 也就是服务端的application name。</p>
<p>application.yml</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">3011</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">management</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">endpoint</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">shutdown</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">endpoints</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">web</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">exposure</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">include</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;*&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">env</span><span class="p">:</span><span class="w"> </span><span class="l">NaN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">user</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">NaN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">NaN</span><span class="w">
</span></span></span></code></pre></div><p>3、启动类，增加了 @EnableEurekaClient 注解，可以用 @EnableDiscoveryClient 代替</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@SpringBootApplication</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableEurekaClient</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">Application</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">SpringApplication</span><span class="p">.</span><span class="na">run</span><span class="p">(</span><span class="n">Application</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>4、另外的配置实体类和 RESTController 和上面的一样，没有任何更改，直接参考即可。</p>
<p>5、启动 client 端，访问 http://localhost:3011/autoShow，即可看到配置文件内容。</p>
<p>这个例子只是介绍了和 Eureka 结合的最基础的情况，还可以注册到高可用的 Eureka 注册中心，另外，配置中心服务端还可以注册多个实例，同时保证注册中心的高可用。</p>
<h2 id="注意事项"><em>注意事项</em></h2>
<p><em>1.</em> 在 git 上的配置文件的名字要和 config 的 client 端的 application name 对应；</p>
<p><em>2.</em> 在结合 eureka 的场景中，关于 eureka 和 git config 相关的配置要放在 bootstrap.yml 中，否则会请求默认的 config server 配置，这是因为当你加了配置中心，服务就要先去配置中心获取配置，而这个时候，application.yml 配置文件还没有开始加载，而 bootstrap.yml 是最先加载的。</p>
]]></content:encoded></item><item><title>5. 服务注册发现、配置中心集一体的 Spring Cloud Consul</title><link>https://www.showcodeba.com/category/SpringCloud/5-%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E5%8F%91%E7%8E%B0%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%E9%9B%86%E4%B8%80%E4%BD%93%E7%9A%84-Spring-Cloud-Consul/</link><pubDate>Fri, 02 Jun 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/SpringCloud/5-%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E5%8F%91%E7%8E%B0%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%E9%9B%86%E4%B8%80%E4%BD%93%E7%9A%84-Spring-Cloud-Consul/</guid><description>前面讲了 Eureka 和 Spring Cloud Config，今天介绍一个全能选手 「Consul」。它是 HashiCorp 公司推出，用于提供服务发现和服务配置的工具。用 go 语言开发，具有很好</description><content:encoded><![CDATA[<blockquote>
<p>前面讲了 Eureka 和 Spring Cloud Config，今天介绍一个全能选手 「Consul」。它是 HashiCorp 公司推出，用于提供服务发现和服务配置的工具。用 go 语言开发，具有很好的可移植性。被 Spring Cloud 纳入其中，Eureka 停止新版本开发，更多的想让开发者使用 Consul 来作为服务注册发现使用。</p>
</blockquote>
<p>Consul 提供的功能包括如下几个：</p>
<p><strong>服务发现</strong></p>
<p>Consul 让服务注册和服务发现（通过 DNS 和 HTTP 接口）更加简单，甚至对于外部服务（例如SaaS）注册也一样。</p>
<p><strong>故障检测</strong></p>
<p>通过健康检查，服务发现可以防止请求被路由到不健康的主机，并且可以使服务容易断开（不再提供服务）。</p>
<p><strong>多数据中心</strong></p>
<p>Consul 不需要复杂的配置即可简便的扩展到多个数据中心，查找其它数据中心的服务或者只请求当前数据中心的服务。</p>
<p><strong>键值存储</strong></p>
<p>灵活的键值存储，提供动态配置、特征标记、协作、leader 选举等功能，通过长轮询实现配置改变的即时通知。</p>
<p>Spring Cloud Consul 将 Consul 进行自动配置和进一步封装。</p>
<p>Spring Cloud Consul 可替代已有的 Spring Cloud Eureka，也就是当做服务注册发现框架使用。并且 Eureka 2.x 版本也已经停止开发，并且 Spring Cloud 官方也建议用 Spring Cloud Consul 来替代，当然如果已经用了 Eureka 在项目中也没有关系，Eureka 已经足够稳定，正常使用没有任何问题。</p>
<p>Spring Cloud Consul 可替代已有的 Spring Cloud Config ，也就是当做配置中心使用。</p>
<p>Spring Cloud Consul 主要用作服务注册发现，并且官方建议替代 Eureka，那么它肯定具有 Eureka 或其他框架不具备的优势，下面看一下对比它和其他服务发现方式做的一下对比(摘自网络):</p>
<table>
<thead>
<tr>
<th>功能点</th>
<th>euerka</th>
<th>Consul</th>
<th>zookeeper</th>
<th>etcd</th>
</tr>
</thead>
<tbody>
<tr>
<td>服务健康检查</td>
<td>可配支持</td>
<td>服务状态，内存，硬盘等</td>
<td>(弱)长连接，keepalive</td>
<td>连接心跳</td>
</tr>
<tr>
<td>多数据中心</td>
<td>—</td>
<td>支持</td>
<td>—</td>
<td>—</td>
</tr>
<tr>
<td>kv 存储服务</td>
<td>—</td>
<td>支持</td>
<td>支持</td>
<td>支持</td>
</tr>
<tr>
<td>一致性</td>
<td>—</td>
<td>raft</td>
<td>paxos</td>
<td>raft</td>
</tr>
<tr>
<td>cap</td>
<td>ap（高可用、分区容错）</td>
<td>ca（数据一致、高可用）</td>
<td>cp</td>
<td>cp</td>
</tr>
<tr>
<td>使用接口(多语言能力)</td>
<td>http（sidecar）</td>
<td>支持 http 和 dns</td>
<td>客户端</td>
<td>http/grpc</td>
</tr>
<tr>
<td>watch 支持</td>
<td>支持 long polling/大部分增量</td>
<td>全量/支持long polling</td>
<td>支持</td>
<td>支持 long polling</td>
</tr>
<tr>
<td>自身监控</td>
<td>metrics</td>
<td>metrics</td>
<td>—</td>
<td>metrics</td>
</tr>
<tr>
<td>安全</td>
<td>—</td>
<td>acl /https</td>
<td>acl</td>
<td>https 支持（弱）</td>
</tr>
<tr>
<td>spring cloud 集成</td>
<td>已支持</td>
<td>已支持</td>
<td>已支持</td>
<td>已支持</td>
</tr>
</tbody>
</table>
<p>Consul 采用 raft 算法来保证数据的强一致性，如此带来的优势很明显，相应的也带来了一些牺牲：</p>
<ol>
<li>服务注册相比 Eureka 会稍慢一些。因为 Consul 的 raft 协议要求必须过半数的节点都写入成功才认为注册成功；</li>
<li>Leader挂掉时，重新选举期间整个 consul 不可用，以此保证了强一致性但牺牲了可用性。</li>
</ol>
<h2 id="consul-的安装和启动"><strong>Consul 的安装和启动</strong></h2>
<p>与 Eureka 不同，Consul 需要独立安装，可以到官网（https://www.consul.io/downloads.html）下载。具体操作系统的安装方式不同，可参考官网。</p>
<p>Consul 提供了一系列的参数，用于在命令行执行。Consul 默认提供了 web UI 界面来查看配置。通过访问 server 的 8500 端口可以访问到 web UI 控制台。</p>
<p>开发过程中，我们可以通过命令 <code>consul agent -dev</code> 来启动开发模式，启动成功后，访问 localhost:8500 可以看到当前 consul 的所有服务。如下图：
<img src="https://hexo.moonkite.cn/blog/273364-20190827215917671-1664797359.png"></p>
<p>更多的在生成环境的部署可自行搜索相关介绍，这里暂时只用 dev 模式启动，用来介绍 Spring Cloud Consul 的使用。</p>
<h2 id="实现服务提供者"><strong>实现服务提供者</strong></h2>
<p>1、引用 spring-cloud-consul</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-consul-all<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-web<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-actuator<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependencyManagement&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;dependencies&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;artifactId&gt;</span>spring-cloud-consul-dependencies<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;version&gt;</span>2.1.0.M2<span class="nt">&lt;/version&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;type&gt;</span>pom<span class="nt">&lt;/type&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;scope&gt;</span>import<span class="nt">&lt;/scope&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/dependencies&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependencyManagement&gt;</span>
</span></span></code></pre></div><p>2、设置 consul 相关配置，在 bootstrap.yml 配置文件中，配置如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">consul</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">discovery</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">service-name</span><span class="p">:</span><span class="w"> </span><span class="l">consul-provider </span><span class="w"> </span><span class="c">## 服务提供者名称</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">localhost                 </span><span class="w"> </span><span class="c">## consul 所在服务地址</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8500</span><span class="w">					   </span><span class="c">## consul 端口</span><span class="w">
</span></span></span></code></pre></div><p>3、设置 server 相关配置，在 application.yml 配置文件中，配置如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">consul-provider</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">5000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">endpoints</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">health</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">sensitive</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">restart</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">shutdown</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">management</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">security</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span></code></pre></div><p>4、增加一个 RestController ，写两个测试服务方法</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@RestController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@Slf4j</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">HelloController</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">DiscoveryClient</span><span class="w"> </span><span class="n">discoveryClient</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;test&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">test</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">List</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">services</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">discoveryClient</span><span class="p">.</span><span class="na">getServices</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="n">services</span><span class="p">){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="n">s</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;hello spring cloud!&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;nice&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">nice</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">List</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">services</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">discoveryClient</span><span class="p">.</span><span class="na">getServices</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="n">services</span><span class="p">){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">&#34;gogogo&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">s</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;nice to meet you!&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>5、Spring boot 启动类</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@SpringBootApplication</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableDiscoveryClient</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">Application</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">SpringApplication</span><span class="p">.</span><span class="na">run</span><span class="p">(</span><span class="n">Application</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>@EnableDiscoveryClient 注解标示这是一个 client 端。</p>
<p>启动这个服务提供者，打开 http://localhost:8500 可以看到这个服务
<img src="https://hexo.moonkite.cn/blog/273364-20190827215945600-2102829205.png"></p>
<p>##<strong>实现服务消费者</strong></p>
<p>1、引用相关 maven 包，除了引用与上面服务提供者相同的包外，还引用了 openFeign</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-openfeign<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>2、bootstrap.yml 配置，因为作为服务消费者，所以设置不注册到 consul</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">consul</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">discovery</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">register</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span></code></pre></div><p>3、application.yml 配置</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">consul-customer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">5001</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">endpoints</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">health</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">sensitive</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">restart</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">shutdown</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">management</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">security</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span></code></pre></div><p>4、项目启动类</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@SpringBootApplication</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableDiscoveryClient</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableFeignClients</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">Application</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Bean</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@LoadBalanced</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">RestTemplate</span><span class="w"> </span><span class="nf">restTemplate</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">RestTemplate</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">SpringApplication</span><span class="p">.</span><span class="na">run</span><span class="p">(</span><span class="n">Application</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>使用 @EnableDiscoveryClient 注解表示作为服务 client 端，@EnableFeignClients 启用 openFeign 。</p>
<p>5、新建一个 openFeign 服务接口</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@FeignClient</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;consul-provider&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">interface</span> <span class="nc">IHelloService</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@RequestMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;/hello&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="nf">hello</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@RequestMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;nice&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="nf">nice</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>对应服务提供者中提供的两个 RESTful 接口地址</p>
<p>6、实现一个 RestController 来访问服务提供者开放出来的服务</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@RestController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">ConsumerController</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">LoadBalancerClient</span><span class="w"> </span><span class="n">loadBalancer</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">DiscoveryClient</span><span class="w"> </span><span class="n">discoveryClient</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">RestTemplate</span><span class="w"> </span><span class="n">restTemplate</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">IHelloService</span><span class="w"> </span><span class="n">helloService</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">SERVICE_NAME</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;consul-provider&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 使用普通的 RestTemplate 方法访问服务
</span></span></span><span class="line"><span class="cl"><span class="cm">     *
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @return
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;test&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">test</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">restTemplate</span><span class="p">.</span><span class="na">getForObject</span><span class="p">(</span><span class="s">&#34;http://&#34;</span><span class="o">+</span><span class="n">SERVICE_NAME</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">&#34;/test&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">String</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="n">result</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">result</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 使用 openFeign 方式访问服务
</span></span></span><span class="line"><span class="cl"><span class="cm">     *
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @return
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;feign&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">feign</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">helloService</span><span class="p">.</span><span class="na">nice</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">s</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 获取所有服务实例
</span></span></span><span class="line"><span class="cl"><span class="cm">     *
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @return
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;/services&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">services</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">discoveryClient</span><span class="p">.</span><span class="na">getInstances</span><span class="p">(</span><span class="n">SERVICE_NAME</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 从所有服务中选择一个服务（轮询）
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;/choose&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">choose</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">loadBalancer</span><span class="p">.</span><span class="na">choose</span><span class="p">(</span><span class="n">SERVICE_NAME</span><span class="p">).</span><span class="na">getUri</span><span class="p">().</span><span class="na">toString</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>启动消费者程序，然后访问对应的 RESTful 接口，可以得到对应的结果。</p>
<h2 id="实现高可用服务提供者"><strong>实现高可用服务提供者</strong></h2>
<p>线上的微服务最好不要是单点形式，接下来通过配置来启动两个服务提供者，只要保证 service-name 相同，就表示这是同一个服务。</p>
<p>1、 bootstrap.yml 配置不变</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">consul</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">discovery</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">service-name</span><span class="p">:</span><span class="w"> </span><span class="l">consul-provider</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">localhost</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8500</span><span class="w">
</span></span></span></code></pre></div><p>2、application.yml 修改为如下配置</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">profiles</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">active</span><span class="p">:</span><span class="w"> </span><span class="l">consul-provider1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">endpoints</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">health</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">sensitive</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">restart</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">shutdown</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">management</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">security</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">profiles</span><span class="p">:</span><span class="w"> </span><span class="l">consul-provider1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">consul-provider1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">5000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">profiles</span><span class="p">:</span><span class="w"> </span><span class="l">consul-provider2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">consul-provider2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">5002</span><span class="w">
</span></span></span></code></pre></div><p>3、之后启动的时候加上 vm 参数。分别加上参数：</p>
<p>-Dspring.profiles.active=consul-provider1</p>
<p>-Dspring.profiles.active=consul-provider2</p>
<p>分别在 5000 端口和 5002 端口启动服务提供者 consul-provider</p>
<p>4、最后仍然访问消费者的 RESTful 接口地址，可以在服务提供者后台看到每次请求调用的服务实例。</p>
<h2 id="用作配置中心"><strong>用作配置中心</strong></h2>
<p>我们知道，Spring Cloud Config 提供了配置中心的功能，但是需要配合 git、svn 或外部存储（例如各种数据库），那么既然使用了 Consul ，就可以使用 Consul 提供的配置中心功能，并且不需要额外的 git 、svn、数据库等配合使用。</p>
<p>接下来，简单介绍一下 Spring Cloud Consul 如何用作配置中心。Consul 支持 yaml 和 properties 格式的配置文件内容，本例中以 yaml 格式为例。</p>
<p>1、引用相关的 maven 包</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-consul-config<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-web<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-actuator<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>2、bootstrap.yml 配置，这里主要设置有关 config 的参数</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">consul</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">config</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">    </span><span class="c"># 启用配置中心</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">format</span><span class="p">:</span><span class="w"> </span><span class="l">yaml    </span><span class="w"> </span><span class="c"># 指定配置格式为 yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">data-key</span><span class="p">:</span><span class="w"> </span><span class="l">mysql_config</span><span class="w"> </span><span class="c"># 也就是 consul 中 key/value 中的 key</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">prefix</span><span class="p">:</span><span class="w"> </span><span class="l">config        </span><span class="w"> </span><span class="c"># 可以理解为配置文件所在的最外层目录</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">defaultContext</span><span class="p">:</span><span class="w"> </span><span class="l">consul-config </span><span class="w"> </span><span class="c"># 可以理解为 mysql_config 的上级目录</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">discovery</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">register</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span></code></pre></div><p>对应到 consul 上，key/value 里的配置如下：
<img src="https://hexo.moonkite.cn/blog/273364-20190827220016543-942585908.png"></p>
<p>3、application.yml 配置文件内容</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">consul-config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">5008</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">endpoints</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">health</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">sensitive</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">restart</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">shutdown</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">management</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">security</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span></code></pre></div><p>4、定义配置文件实体类，指定 @ConfigurationProperties 注解，指定前缀为 mysql，也就是 key/value 配置文件中的顶层 key。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Component</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@ConfigurationProperties</span><span class="p">(</span><span class="n">prefix</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;mysql&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">MySqlComplexConfig</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">class</span> <span class="nc">UserInfo</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">private</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">username</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">private</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">password</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">getUsername</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="n">username</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">setUsername</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">username</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">this</span><span class="p">.</span><span class="na">username</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">username</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">getPassword</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="n">password</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">setPassword</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">password</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">this</span><span class="p">.</span><span class="na">password</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">password</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">toString</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;UserInfo{&#34;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;username=&#39;&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">username</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="sc">&#39;\&#39;&#39;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="s">&#34;, password=&#39;&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">password</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="sc">&#39;\&#39;&#39;</span><span class="w"> </span><span class="o">+</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="sc">&#39;}&#39;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">host</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">UserInfo</span><span class="w"> </span><span class="n">user</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">getHost</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">host</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">setHost</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">host</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">this</span><span class="p">.</span><span class="na">host</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">host</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">UserInfo</span><span class="w"> </span><span class="nf">getUser</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">user</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">setUser</span><span class="p">(</span><span class="n">UserInfo</span><span class="w"> </span><span class="n">user</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">this</span><span class="p">.</span><span class="na">user</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">user</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>5、新建一个 RestController 来获取输出 value 内容</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@RestController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@Slf4j</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">ConfigController</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">MySqlConfig</span><span class="w"> </span><span class="n">mySqlConfig</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">MySqlComplexConfig</span><span class="w"> </span><span class="n">mySqlComplexConfig</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;mysqlhost&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">getMysqlHost</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">mySqlConfig</span><span class="p">.</span><span class="na">getHost</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;mysqluser&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">getMysqlUser</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="n">mySqlComplexConfig</span><span class="p">.</span><span class="na">getHost</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">MySqlComplexConfig</span><span class="p">.</span><span class="na">UserInfo</span><span class="w"> </span><span class="n">userInfo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">mySqlComplexConfig</span><span class="p">.</span><span class="na">getUser</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">userInfo</span><span class="p">.</span><span class="na">toString</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>6、最后，启动应用，访问 RestController 中的 RESTful 接口即可看到配置文件内容。</p>
<p>与 Spring Cloud Config 相比，Consul 在控制台修改配置后，会立即更新，不用再结合 Spring Cloud Bus 之类的配合了。</p>
<p><a href="https://github.com/huzhicheng/spring-cloud-study/tree/master/consul">源码地址</a></p>
]]></content:encoded></item><item><title>6. Spring Cloud 系列之 Spring Cloud Stream</title><link>https://www.showcodeba.com/category/SpringCloud/6-Spring-Cloud-%E7%B3%BB%E5%88%97%E4%B9%8B-Spring-Cloud-Stream/</link><pubDate>Fri, 02 Jun 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/SpringCloud/6-Spring-Cloud-%E7%B3%BB%E5%88%97%E4%B9%8B-Spring-Cloud-Stream/</guid><description>Spring Cloud Stream 是消息中间件组件，它集成了 kafka 和 rabbitmq 。本篇文章以 Rabbit MQ 为消息中间件系统为基础，介绍 Spring Cloud Stream 的使用。如果你没有用过消息中间件，可以到 RabbitMQ 的官网看一</description><content:encoded><![CDATA[<blockquote>
<p>Spring Cloud Stream 是消息中间件组件，它集成了 kafka 和 rabbitmq 。本篇文章以 Rabbit MQ 为消息中间件系统为基础，介绍 Spring Cloud Stream 的使用。如果你没有用过消息中间件，可以到 RabbitMQ 的官网看一下，或者参考这个 <a href="http://rabbitmq.mr-ping.com/">http://rabbitmq.mr-ping.com/</a>。理解了消息中间件的设计，才能更好的使用它。</p>
</blockquote>
<h2 id="消息中间的几大应用场景"><strong>消息中间的几大应用场景</strong></h2>
<p>1、异步处理</p>
<p>比如用户在电商网站下单，下单完成后会给用户推送短信或邮件，发短信和邮件的过程就可以异步完成。因为下单付款是核心业务，发邮件和短信并不属于核心功能，并且可能耗时较长，所以针对这种业务场景可以选择先放到消息队列中，有其他服务来异步处理。</p>
<p>2、应用解耦：</p>
<p>假设公司有几个不同的系统，各系统在某些业务有联动关系，比如 A 系统完成了某些操作，需要触发 B 系统及 C 系统。如果 A 系统完成操作，主动调用 B 系统的接口或 C 系统的接口，可以完成功能，但是各个系统之间就产生了耦合。用消息中间件就可以完成解耦，当 A 系统完成操作将数据放进消息队列，B 和 C 系统去订阅消息就可以了。这样各系统只要约定好消息的格式就好了。</p>
<p>3、流量削峰</p>
<p>比如秒杀活动，一下子进来好多请求，有的服务可能承受不住瞬时高并发而崩溃，所以针对这种瞬时高并发的场景，在中间加一层消息队列，把请求先入队列，然后再把队列中的请求平滑的推送给服务，或者让服务去队列拉取。</p>
<p>4、日志处理</p>
<p>kafka 最开始就是专门为了处理日志产生的。</p>
<p>当碰到上面的几种情况的时候，就要考虑用消息队列了。如果你碰巧使用的是 RabbitMQ 或者 kafka ，而且同样也是在使用 Spring Cloud ，那可以考虑下用 Spring Cloud Stream。</p>
<h2 id="使用-spring-cloud-stream--rabbitmq"><strong>使用 Spring Cloud Stream &amp;&amp; RabbitMQ</strong></h2>
<p>介绍下面的例子之前，假定你已经对 RabbitMQ 有一定的了解。</p>
<p>首先来认识一下 Spring Cloud Stream 中的几个重要概念。</p>
<p><em>Destination Binders</em>：目标绑定器，目标指的是 kafka 还是 RabbitMQ，绑定器就是封装了目标中间件的包。如果操作的是 kafka 就使用 kafka binder ，如果操作的是 RabbitMQ 就使用 rabbitmq binder。</p>
<p><em>Destination Bindings</em>：外部消息传递系统和应用程序之间的桥梁，提供消息的“生产者”和“消费者”（由目标绑定器创建）</p>
<p><em>Message</em>：一种规范化的数据结构，生产者和消费者基于这个数据结构通过外部消息系统与目标绑定器和其他应用程序通信。</p>
<p><img src="https://hexo.moonkite.cn/blog/273364-20190924095629304-2092960942.png"></p>
<p>可能看完了上面的三个概念仍然是一头雾水，没有关系，实践过程中自然就明白了。</p>
<h2 id="先来一个最简单的例子"><strong>先来一个最简单的例子</strong></h2>
<p>因为用到的是 rabbitmq，所以在本地搭好 rabbitmq 环境，然后装好 rabbitmq-management 插件，这样就可以访问 web UI 界面了，默认是 15672 端口。</p>
<p>1、引用对应 rabbitmq 的 stream 包</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span> 
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>  
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-stream-rabbit<span class="nt">&lt;/artifactId&gt;</span> 
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>2、在 application.yml 中增加配置</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">profiles</span><span class="p">:</span><span class="w"> </span><span class="l">stream-rabbit-customer-group1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">stream</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">bindings</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">input</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">destination</span><span class="p">:</span><span class="w"> </span><span class="l">default.messages</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">binder</span><span class="p">:</span><span class="w"> </span><span class="l">local_rabbit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">output</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">destination</span><span class="p">:</span><span class="w"> </span><span class="l">default.messages</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">binder</span><span class="p">:</span><span class="w"> </span><span class="l">local_rabbit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">binders</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">local_rabbit</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">rabbit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">rabbitmq</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">localhost</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">32775</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">guest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">guest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8201</span><span class="w">
</span></span></span></code></pre></div><p>理解配置文件很重要，基本上理解清楚了配置，也就明白 spring cloud stream 是怎么回事了。</p>
<p><code>spring.cloud.stream.binders</code>，上面提到了 stream 的 3 个重要概念的第一个 「Destination binders」。上面的配置文件中就配置了一个 binder，命名为 local_rabbit，指定 type 为 rabbit ，表示使用的是 rabbitmq 消息中间件，如果用的是 kafka ，则 type 设置为 kafka。environment 就是设置使用的消息中间件的配置信息，包括 host、port、用户名、密码等。可以设置多了个 binder，适配不同的场景。</p>
<p><code>spring.cloud.stream.bindings</code> ，对应上面提到到 「Destination Bindings」。这里面可以配置多个 input 或者 output，分别表示消息的接收通道和发送通道，对应到 rabbitmq 上就是不同的 exchange。这个配置文件里定义了两个input 、两个output，名称分别为 input、log_input、output、log_output。这个名称不是乱起的，在我们的程序代码中会用到，用来标示某个方法接收哪个 exchange 或者发送到哪个 exchange 。</p>
<p>每个通道下的 destination 属性指 exchange 的名称，binder 指定在 binders 里设置的 binder，上面配置中指定了 local_rabbit 。</p>
<p>可以看到 input、output 对应的 destination 是相同的，log_input、log_output 对应的 destination 也相同， 也就是对应相同的 exchange。一个表示消息来源，一个表示消息去向。</p>
<p>另外还可以设置 group 。因为服务很可能不止一个实例，如果启动多个实例，那么没必要每个实例都消费同一个消息，只要把功能相同的实例的 group 设置为同一个，那么就会只有一个实例来消费消息，避免重复消费的情况。如果设置了 group，那么 group 名称就会成为 queue 的名称，如果没有设置 group ，那么 queue 就会根据 destination + 随机字符串的方式命名。</p>
<p>3、接下来做一个最简单的例子，来演示如何接收消息。</p>
<p>首先来介绍一下 stream 内置的简单消息通道（消息通道也就是指消息的来源和去向）接口定义，一个 Source 和 一个 Sink 。</p>
<p><strong>Source.java</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">org.springframework.cloud.stream.annotation.Output</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">org.springframework.messaging.MessageChannel</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">interface</span> <span class="nc">Source</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="n">OUTPUT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;output&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Output</span><span class="p">(</span><span class="s">&#34;output&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">MessageChannel</span><span class="w"> </span><span class="nf">output</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>消息发送通道定义，定义了一个 MessageChannel 类型的 output() 方法，用 <code>@Output</code> 注解标示，并指定了 binding 的名称为 output。</p>
<p><strong>Sink.java</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">org.springframework.cloud.stream.annotation.Input</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">org.springframework.messaging.SubscribableChannel</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">interface</span> <span class="nc">Sink</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="n">INPUT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;input&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Input</span><span class="p">(</span><span class="s">&#34;input&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">SubscribableChannel</span><span class="w"> </span><span class="nf">input</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>消息接收通道定义，定义了一个 SubscribableChannel 类型的 input() 方法，表示订阅一个消息的方法，并用 <code>@Input</code> 注解标识，并且指定了 binging 的名称为 input 。</p>
<p>创建一个简单的消息接收方法：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@SpringBootApplication</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableBinding</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="n">Processor</span><span class="p">.</span><span class="na">class</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@Slf4j</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">DefaultApplication</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">SpringApplication</span><span class="p">.</span><span class="na">run</span><span class="p">(</span><span class="n">DefaultApplication</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>在项目启动类上加上注解 <code>@EnableBinding(value = {Processor.class})</code> ，表明启用 stream ，并指定定义的 Channel 定义接口类。</p>
<p>然后，创建一个 service 服务类，用来订阅消息，并对消息进行处理。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Slf4j</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@Component</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">DefaultMessageListener</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@StreamListener</span><span class="p">(</span><span class="n">Processor</span><span class="p">.</span><span class="na">INPUT</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">processMyMessage</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">message</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">&#34;接收到消息：&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">message</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>在方法 <code>processMyMessage()</code> 上使用 <code>@StreamListener</code> 注解，表示对消息进行订阅监控，指定 binding 的名称，其中 Processor.INPUT 就是 Sink 的 input ，也就是字符串 <code>input</code> ,对应的上面的配置文件，就是 spring.cloud.stream.bindings.input。</p>
<p>启动 DefaultApplication ，可以在 rabbitmq 管理控制台的 exchanges 中看到增加的这几个 bindings 。</p>
<p><img src="https://hexo.moonkite.cn/blog/273364-20190924095714758-1384799908.png"></p>
<p>可以看到 exchange 的名称对应的就是 bindings 的两个 input 和 两个 output 的 destination 的值。</p>
<p><strong>用 rabbitmq UI 控制台发送消息测试</strong></p>
<p>点击上图的 default.input.messages 进入 exchange 详请页面，在 publish message 部分填写上 Payload ，然后点击 Publish message 按钮。</p>
<p><img src="https://hexo.moonkite.cn/blog/273364-20190924095740025-1689503088.png"></p>
<p>之后回到 DefaultApplication 的输出控制台，会看到消息已经被接收。
<img src="https://hexo.moonkite.cn/blog/273364-20190924095756361-682131929.png"></p>
<h2 id="模拟一个日志处理"><strong>模拟一个日志处理</strong></h2>
<p>接下来模拟生产者和消费者处理消息的过程，模拟一个日志处理的过程。</p>
<ul>
<li>原始日志发送到  kite.log.messages exchange</li>
<li>接收器在 kite.log.messages exchange 接收原始日志，经过处理格式化，发送到 kite.log.format.messages exchange</li>
<li>接收器在  kite.log.format.messages exchange 接收格式化后的日志</li>
</ul>
<p>1、自定义消息通道接口，上面介绍了 stream 自带的 Sink 和 Source，也仅仅能做个演示，真正的业务中还是需要自己定义更加灵活的接口。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Component</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">interface</span> <span class="nc">MyProcessor</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="n">MESSAGE_INPUT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;log_input&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="n">MESSAGE_OUTPUT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;log_output&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="n">LOG_FORMAT_INPUT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;log_format_input&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="n">LOG_FORMAT_OUTPUT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;log_format_output&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Input</span><span class="p">(</span><span class="n">MESSAGE_INPUT</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">SubscribableChannel</span><span class="w"> </span><span class="nf">logInput</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Output</span><span class="p">(</span><span class="n">MESSAGE_OUTPUT</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">MessageChannel</span><span class="w"> </span><span class="nf">logOutput</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Input</span><span class="p">(</span><span class="n">LOG_FORMAT_INPUT</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">SubscribableChannel</span><span class="w"> </span><span class="nf">logFormatInput</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Output</span><span class="p">(</span><span class="n">LOG_FORMAT_OUTPUT</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">MessageChannel</span><span class="w"> </span><span class="nf">logFormatOutput</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>2、创建消费者应用</p>
<p>**配置文件如下 **：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">profiles</span><span class="p">:</span><span class="w"> </span><span class="l">stream-rabbit-customer-group1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">stream</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">bindings</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">log_input</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">destination</span><span class="p">:</span><span class="w"> </span><span class="l">kite.log.messages</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">binder</span><span class="p">:</span><span class="w"> </span><span class="l">local_rabbit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">group</span><span class="p">:</span><span class="w"> </span><span class="l">logConsumer-group1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">log_output</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">destination</span><span class="p">:</span><span class="w"> </span><span class="l">kite.log.messages</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">binder</span><span class="p">:</span><span class="w"> </span><span class="l">local_rabbit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">group</span><span class="p">:</span><span class="w"> </span><span class="l">logConsumer-group1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">log_format_input</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">destination</span><span class="p">:</span><span class="w"> </span><span class="l">kite.log.format.messages</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">binder</span><span class="p">:</span><span class="w"> </span><span class="l">local_rabbit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">group</span><span class="p">:</span><span class="w"> </span><span class="l">logFormat-group1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">log_format_input</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">destination</span><span class="p">:</span><span class="w"> </span><span class="l">kite.log.format.messages</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">binder</span><span class="p">:</span><span class="w"> </span><span class="l">local_rabbit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">group</span><span class="p">:</span><span class="w"> </span><span class="l">logFormat-group1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">binders</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">local_rabbit</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">rabbit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">rabbitmq</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">localhost</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">32775</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">guest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">guest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8201</span><span class="w">
</span></span></span></code></pre></div><p>此配置文件要参照 MyProcessor 接口查看，定义了 4 个 binding，但是 destination 两两相同，也就是两个 exchange。</p>
<p><strong>创建 spring boot 启动类</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@SpringBootApplication</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableBinding</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="n">MyProcessor</span><span class="p">.</span><span class="na">class</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@Slf4j</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">CustomerApplication</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">SpringApplication</span><span class="p">.</span><span class="na">run</span><span class="p">(</span><span class="n">CustomerApplication</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>用 @EnableBinding(value = {MyProcessor.class}) 注解引入 MyProcessor</p>
<p><strong>创建消息接收处理服务</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Slf4j</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@Component</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">LogMessageListener</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 通过 MyProcessor.MESSAGE_INPUT 接收消息
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 然后通过 SendTo 将处理后的消息发送到 MyProcessor.LOG_FORMAT_OUTPUT
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @param message
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @return
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@StreamListener</span><span class="p">(</span><span class="n">MyProcessor</span><span class="p">.</span><span class="na">MESSAGE_INPUT</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@SendTo</span><span class="p">(</span><span class="n">MyProcessor</span><span class="p">.</span><span class="na">LOG_FORMAT_OUTPUT</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">processLogMessage</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">message</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">&#34;接收到原始消息：&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">message</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;「&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">message</span><span class="w"> </span><span class="o">+</span><span class="s">&#34;」&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 接收来自 MyProcessor.LOG_FORMAT_INPUT 的消息
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 也就是加工后的消息，也就是通过上面的 SendTo 发送来的
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 因为 MyProcessor.LOG_FORMAT_OUTPUT 和 MyProcessor.LOG_FORMAT_INPUT 是指向同一 exchange
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @param message
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@StreamListener</span><span class="p">(</span><span class="n">MyProcessor</span><span class="p">.</span><span class="na">LOG_FORMAT_INPUT</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">processFormatLogMessage</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">message</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">&#34;接收到格式化后的消息：&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">message</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>3、创建一个消息生产者，用于发送原始日志消息</p>
<p><strong>配置文件</strong>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">stream</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">bindings</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">log_output</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">destination</span><span class="p">:</span><span class="w"> </span><span class="l">kite.log.messages</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">binder</span><span class="p">:</span><span class="w"> </span><span class="l">local_rabbit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">group</span><span class="p">:</span><span class="w"> </span><span class="l">logConsumer-group1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">binders</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">local_rabbit</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">rabbit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">rabbitmq</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">localhost</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">32775</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">guest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">guest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8202</span><span class="w">
</span></span></span></code></pre></div><p>仅仅指定了一个 binding log_output，用来发送消息，如果只做生产者就不要指定 log_input，如果指定了 log_input ，应用就会认为这个生产者服务也会消费消息，如果这时没有在此服务中订阅消息，当消息被发送到这个服务时，因为并没有订阅消息，也就是没有 @StreamListener 注解的方法，就会出现如下异常：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers <span class="k">for</span> channel
</span></span></code></pre></div><p><strong>创建 spring boot 启动类</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Slf4j</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@RestController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableBinding</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="n">MyProcessor</span><span class="p">.</span><span class="na">class</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">MyMessageController</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">MyProcessor</span><span class="w"> </span><span class="n">myProcessor</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;sendLogMessage&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">sendLogMessage</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">message</span><span class="p">){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Message</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">stringMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">org</span><span class="p">.</span><span class="na">springframework</span><span class="p">.</span><span class="na">messaging</span><span class="p">.</span><span class="na">support</span><span class="p">.</span><span class="na">MessageBuilder</span><span class="p">.</span><span class="na">withPayload</span><span class="p">(</span><span class="n">message</span><span class="p">).</span><span class="na">build</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">myProcessor</span><span class="p">.</span><span class="na">logOutput</span><span class="p">().</span><span class="na">send</span><span class="p">(</span><span class="n">stringMessage</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>同样的引入 @EnableBinding(value = {MyProcessor.class})</p>
<p><strong>创建一个 Controller 用来发送消息</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Slf4j</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@RestController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableBinding</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="n">MyProcessor</span><span class="p">.</span><span class="na">class</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">MyMessageController</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">MyProcessor</span><span class="w"> </span><span class="n">myProcessor</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;sendLogMessage&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">sendLogMessage</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">message</span><span class="p">){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Message</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">stringMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">org</span><span class="p">.</span><span class="na">springframework</span><span class="p">.</span><span class="na">messaging</span><span class="p">.</span><span class="na">support</span><span class="p">.</span><span class="na">MessageBuilder</span><span class="p">.</span><span class="na">withPayload</span><span class="p">(</span><span class="n">message</span><span class="p">).</span><span class="na">build</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">myProcessor</span><span class="p">.</span><span class="na">logOutput</span><span class="p">().</span><span class="na">send</span><span class="p">(</span><span class="n">stringMessage</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>之后，访问链接：</p>
<p>http://localhost:8202/sendLogMessage?message=原始日志</p>
<p>可以在消费服务端看到如下输出：
<img src="https://hexo.moonkite.cn/blog/273364-20190924095820387-1315301053.png"></p>
<h2 id="其他"><strong>其他</strong></h2>
<p>消息除了可以是字符串类型，还可以是其他类型，也可以是实体类型，例如</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;sendObjectLogMessage&#34;</span><span class="p">)</span><span class="w"> 
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">sendObjectLogMessage</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">LogInfo</span><span class="w"> </span><span class="n">logInfo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">LogInfo</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">logInfo</span><span class="p">.</span><span class="na">setClientIp</span><span class="p">(</span><span class="s">&#34;192.168.1.111&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">logInfo</span><span class="p">.</span><span class="na">setClientVersion</span><span class="p">(</span><span class="s">&#34;1.0&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">logInfo</span><span class="p">.</span><span class="na">setUserId</span><span class="p">(</span><span class="s">&#34;198663383837434&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">logInfo</span><span class="p">.</span><span class="na">setTime</span><span class="p">(</span><span class="n">Date</span><span class="p">.</span><span class="na">from</span><span class="p">(</span><span class="n">Instant</span><span class="p">.</span><span class="na">now</span><span class="p">()));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Message</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">LogInfo</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">stringMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">org</span><span class="p">.</span><span class="na">springframework</span><span class="p">.</span><span class="na">messaging</span><span class="p">.</span><span class="na">support</span><span class="p">.</span><span class="na">MessageBuilder</span><span class="p">.</span><span class="na">withPayload</span><span class="p">(</span><span class="n">logInfo</span><span class="p">).</span><span class="na">build</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">myProcessor</span><span class="p">.</span><span class="na">logOutput</span><span class="p">().</span><span class="na">send</span><span class="p">(</span><span class="n">stringMessage</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>上面代码发送了一个 LogInfo 实体对象，在消费者端依然可以用字符串类型接收，因为 @StreamListener 注解会默认把实体转为 json 字符串。</p>
<p>另外，可以试着启动两个消费者端，把 group 设置成相同的，这时，发送的消息只会被一个消费者接收。</p>
<p>如果把 group 设置成不一样的，那么发送的消息会被两个消费者接收。</p>
]]></content:encoded></item><item><title>7. 网关我选 Spring Cloud Gateway</title><link>https://www.showcodeba.com/category/SpringCloud/7-Spring-Cloud-Gateway-%E7%BD%91%E5%85%B3%E7%9A%84%E4%BD%BF%E7%94%A8/</link><pubDate>Fri, 02 Jun 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/SpringCloud/7-Spring-Cloud-Gateway-%E7%BD%91%E5%85%B3%E7%9A%84%E4%BD%BF%E7%94%A8/</guid><description>网关可提供请求路由与组合、协议转换、安全认证、服务鉴权、流量控制与日志监控等服务。可选的网关有不少，比如 Nginx、高性能网关 OpenRes</description><content:encoded><![CDATA[<p>网关可提供<strong>请求路由与组合</strong>、<strong>协议转换</strong>、<strong>安全认证</strong>、<strong>服务鉴权</strong>、<strong>流量控制</strong>与<strong>日志监控</strong>等服务。可选的网关有不少，比如 Nginx、高性能网关 OpenResty、Linkerd 以及 Spring Cloud Gateway。</p>
<p>如果是真的追求高性能，那肯定是选择 Nginx 或者 OpenResty 无疑了， 但是对性能要求不是很高的话，并且又在用 Spring Cloud 系列，那当然就要选择 Spring Cloud Gateway 了。</p>
<p>网关的基础就是路由功能，通俗解释就是地址转发，将一个请求地址转发到实际的服务地址。比如请求的是 <a href="http://xxx.com/api">http://xxx.com/api</a> 的路由地址，实际上会被转发到 <a href="http://xxx.com:8888">http://xxx.com:8888</a> 上来，这就是个最简单的路由方式。</p>
<p>我们可以理解为 Spring Cloud Gateway 就是针对进来的请求做各种判断和处理，比如说判断请求的合法性、权限验证，请求地址改写，请求参数、头信息、cookie 信息的分析和改写，请求速率控制，日志留存等。而这些都可以方便的通过 Predicate 和 GatewayFilter 来组合实现。</p>
<h3 id="创建-spring-cloud-gateway-项目">创建 Spring Cloud Gateway 项目</h3>
<p>Spring Cloud 版本是 Greenwich.SR2，Spring Boot 版本 2.1.6.RELEASE，JDK 1.8。</p>
<p>接下来正式创建一个 Gateway 项目。</p>
<p><em>首先做两个微服务，当做路由转发的目标服务</em></p>
<p>两个微服务是以 consul 作为服务注册中心的，可以看这篇文章<a href="https://mp.weixin.qq.com/s/HKhzKT4oMX8S_Jg1nLtxNA">服务注册发现、配置中心集一体的 Spring Cloud Consul</a></p>
<p>1、创建 consul-order 服务，具体可以去 github 上看<a href="https://github.com/huzhicheng/spring-cloud-study/tree/master/consul/consul-order">代码</a>，很简单的一个服务。创建的 RESTful Controller 如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@RestController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@RequestMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;order&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">OrderController</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Value</span><span class="p">(</span><span class="s">&#34;${spring.application.name}&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">applicationName</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;get&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">CustomerOrder</span><span class="w"> </span><span class="nf">getOrder</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">CustomerOrder</span><span class="w"> </span><span class="n">customerOrder</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">CustomerOrder</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">customerOrder</span><span class="p">.</span><span class="na">setOrderId</span><span class="p">(</span><span class="s">&#34;9999&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">customerOrder</span><span class="p">.</span><span class="na">setProductName</span><span class="p">(</span><span class="s">&#34;MacBook Pro&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">customerOrder</span><span class="p">.</span><span class="na">setClient</span><span class="p">(</span><span class="n">applicationName</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">customerOrder</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>总之，最后直接访问这个接口的地址为 <a href="http://localhost:5006/order/get">http://localhost:5006/order/get</a></p>
<p>2、创建 consul-user 服务，具体代码可以到<a href="https://github.com/huzhicheng/spring-cloud-study/tree/master/consul/consul-user"> github 上查看</a>。</p>
<p>创建的 RESTful Controller 内容如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@RestController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@RequestMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;user&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">UserController</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;get&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">User</span><span class="w"> </span><span class="nf">getUserInfo</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">User</span><span class="w"> </span><span class="n">user</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">User</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">user</span><span class="p">.</span><span class="na">setName</span><span class="p">(</span><span class="s">&#34;古时的风筝&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">user</span><span class="p">.</span><span class="na">setAge</span><span class="p">(</span><span class="n">8</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">user</span><span class="p">.</span><span class="na">setLocation</span><span class="p">(</span><span class="s">&#34;北京&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">user</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>和上面的微服务有点区别的就是设置了 context-path</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">5005</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">servlet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">context-path</span><span class="p">:</span><span class="w"> </span><span class="l">/user-service</span><span class="w">
</span></span></span></code></pre></div><p>之所以这样不同的设置，是因为下面要验证一个 filter。总之，最后上述接口的访问地址为：<a href="http://localhost:5005/user-service/user/get">http://localhost:5005/user-service/user/get</a></p>
<p><em>创建一个项目，并引入 maven 包</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-gateway<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-webflux<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-actuator<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p><em>配置简单的路由转发</em></p>
<p>路由配置有两种方式。一种是配置文件，另外一种是代码方式配置，WebFlux 的反应式编程方式。所以我们 pom 文件中要引入 WebFlux 的包。这是 Spring 5 的新特性。</p>
<p>1、先看第一种配置文件方式配置：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">10000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">gateway</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloud</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">gateway</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">discovery</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">locator</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">lower-case-service-id</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span><span class="c"># 是否将服务id转换为小写</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">routes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">userServiceRouter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uri</span><span class="p">:</span><span class="w"> </span><span class="l">lb://consul-user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">predicates</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">Path=/user-service/**</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">orderServiceRouter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uri</span><span class="p">:</span><span class="w"> </span><span class="l">lb://consul-order</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">predicates</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">Path=/order-service/**</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">filters</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">StripPrefix=1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">consul</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">localhost</span><span class="w"> </span><span class="c">#注册gateway网关到consul</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8500</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">discovery</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">service-name</span><span class="p">:</span><span class="w"> </span><span class="l">service-gateway</span><span class="w">
</span></span></span></code></pre></div><p>其中包括 Spring Boot 项目的基本配置，name、port ，还有关于 consul 的配置，要将网关服务注册到注册中心。</p>
<p>上面配置中创建了两条路由规则，路由规则名称通过 id 设置，分别是 userServiceRouter 和 orderServiceRouter，通过 predicates.Path 设置待转发的 url，通过 uri 设置转发后的目标地址。上面配置将以<code> /user-service/</code>开头的地址转发到 <code>lb://consul-user </code>,固定格式 lb + 服务id，在有注册中心的情况下要这样写，如过没有注册中心，可以直接写目标 url。</p>
<p>下面的路由规则中多了一个 StripPrefix 的 filter ，这个是 Gateway 的内置 filter，作用就是去掉 Path 中的指定部分，StripPrefix=1，就是以 / 分隔，去掉第一部分，比如 /a/b/c 这个地址，在 StripPrefix=1 的作用下，就会转发到 /b/c/，当  StripPrefix=2 的时候，就会转发到 /c/。</p>
<p>配置好上述接口，然后启动网关服务。访问规则就会有如下对应关系:</p>
<p><a href="http://localhost:10000/user-service/user/get">http://localhost:10000/user-service/user/get</a>-&gt;<a href="http://localhost:5005/user-service/user/get">http://localhost:5005/user-service/user/get</a></p>
<p><a href="http://localhost:10000/order-service/order/get">http://localhost:10000/order-service/order/get</a>-&gt;<a href="http://localhost:5006/order/get">http://localhost:5006/order/get</a></p>
<p>当然这只是针对每一个目标服务只有一个实例的情况，如果有多个实例，就会按照负载策略落到对应的实例中。</p>
<p>2、代码方式的路由配置</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Bean</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="n">RouteLocator</span><span class="w"> </span><span class="nf">kiteRouteLocator</span><span class="p">(</span><span class="n">RouteLocatorBuilder</span><span class="w"> </span><span class="n">builder</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="n">builder</span><span class="p">.</span><span class="na">routes</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">.</span><span class="na">route</span><span class="p">(</span><span class="s">&#34;userRouter&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">r</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">r</span><span class="p">.</span><span class="na">path</span><span class="p">(</span><span class="s">&#34;/user-service/**&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">.</span><span class="na">filters</span><span class="p">(</span><span class="n">f</span><span class="w"> </span><span class="o">-&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="n">f</span><span class="p">.</span><span class="na">addResponseHeader</span><span class="p">(</span><span class="s">&#34;X-CustomerHeader&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;kite&#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">.</span><span class="na">uri</span><span class="p">(</span><span class="s">&#34;lb://consul-user&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">.</span><span class="na">route</span><span class="p">(</span><span class="s">&#34;orderRouter&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">r</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">r</span><span class="p">.</span><span class="na">path</span><span class="p">(</span><span class="s">&#34;/order-service/**&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">.</span><span class="na">filters</span><span class="p">(</span><span class="n">f</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">f</span><span class="p">.</span><span class="na">stripPrefix</span><span class="p">(</span><span class="n">1</span><span class="p">)).</span><span class="na">uri</span><span class="p">(</span><span class="s">&#34;lb://consul-order&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">.</span><span class="na">build</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>上面的这段代码和前面的配置文件的内容是同样的作用。只要实现一个返回类型为 <code>RouteLocator</code>，参数为 <code>RouteLocatorBuilder</code>类型的 Bean。</p>
<p>你看后面那一连串的 r.path().filters().uri() 了吗，用它们就可以简单的配置出路由规则，而且可读性也比较强。另外，Gateway 还套用了 Predicate 的规则来构建更加灵活、复杂的路由规则。Predicate 是 Java 8 增加的逻辑计算库，有 negate()、and()、or()、isEqual()几个方法。具体的代码在 PredicateSpec 和 UriSpec 这两个类里，一目了然。</p>
<p><img src="https://hexo.moonkite.cn/blog/273364-20191011084336725-1715618532.png"></p>
<p>PredicateSpec 里有这么多方法，都可以结合 and、or 组合起来使用。</p>
<p>接下来就说到 filter，Gateway 内置了很多的 filter，可以在 GatewayFilterSpec 类下找到方法封装，每一个 filter 都由一个 factory 的 apply 实现，都在 <code>org.springframework.cloud.gateway.filter.factory</code>包下，有必要的话可以直接看源码。 比如上面用到的 StripPrefix。还有 addResponseHeader，它的作用是在 Response 对象的 header 中添加请求头。</p>
<p><em>启动网关服务</em></p>
<p>启动网关，并访问两个接口测试，接口分别为 http://localhost:10000/user-service/user/get和http://localhost:10000/order-service/order/get，正常返回数据，则说明网关服务配置正常。</p>
<h3 id="巧用-stripprefix-filter">巧用 StripPrefix filter</h3>
<p>微服务多了之后，路由的转发规则也就多了，比方说订单相关请求要转发到订单微服务集群，用户相关请求要转发到用户微服务集群，最终开放给终端的接口也要能表明是哪个微服务的，除了接口文档里说明之外，接口本身最好也能明确标识。</p>
<p>一种方式是在微服务的配置文件中配置上<code>server.servlet.context-path</code>。</p>
<p>还有一种方式就是在路由规则的 path 中配置，然后加上 StripPrefix 配置，选择性的去掉请求 url 中的某些部分。比如我们请求 Gateway的地址为 order-service/order/get，则经过 StripPrefix(1) 之后，会把请求地址变为 order/get，然后根据路由规则定向到具体的微服务地址或者特定的 url。</p>
<p>本篇就介绍 Spring Cloud Gateway 的基本用法，后续还会有关于集成安全认证、鉴权、限流、日志等相关内容，敬请关注。</p>
]]></content:encoded></item><item><title>8. Spring Cloud OAuth2 实现用户认证及单点登录</title><link>https://www.showcodeba.com/category/SpringCloud/8-Spring-Cloud-OAuth2-%E5%AE%9E%E7%8E%B0%E7%94%A8%E6%88%B7%E8%AE%A4%E8%AF%81%E5%8F%8A%E5%8D%95%E7%82%B9%E7%99%BB%E5%BD%95/</link><pubDate>Fri, 02 Jun 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/SpringCloud/8-Spring-Cloud-OAuth2-%E5%AE%9E%E7%8E%B0%E7%94%A8%E6%88%B7%E8%AE%A4%E8%AF%81%E5%8F%8A%E5%8D%95%E7%82%B9%E7%99%BB%E5%BD%95/</guid><description>OAuth 2 有四种授权模式，分别是授权码模式（authorization code）、简化模式（implicit）、密码模式（resource owner password cr</description><content:encoded><![CDATA[<p>OAuth 2 有四种授权模式，分别是授权码模式（authorization code）、简化模式（implicit）、密码模式（resource owner password credentials）、客户端模式（client credentials），具体 OAuth2 是什么，可以参考这篇文章。(<a href="http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html">http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html</a>)</p>
<p><img src="https://hexo.moonkite.cn/blog/bg2014051203.png"></p>
<p>本文我们将使用授权码模式和密码模式两种方式来实现用户认证和授权管理。</p>
<p>OAuth2 其实是一个关于授权的网络标准，它制定了设计思路和运行流程，利用这个标准我们其实是可以自己实现 OAuth2 的认证过程的。今天要介绍的 spring-cloud-starter-oauth2 ，其实是 Spring Cloud 按照 OAuth2 的标准并结合 spring-security 封装好的一个具体实现。</p>
<h3 id="什么情况下需要用-oauth2">什么情况下需要用 OAuth2</h3>
<p>首先大家最熟悉的就是几乎每个人都用过的，比如用微信登录、用 QQ 登录、用微博登录、用 Google 账号登录、用 github 授权登录等等，这些都是典型的 OAuth2 使用场景。假设我们做了一个自己的服务平台，如果不使用 OAuth2 登录方式，那么我们需要用户先完成注册，然后用注册号的账号密码或者用手机验证码登录。而使用了 OAuth2 之后，相信很多人使用过、甚至开发过公众号网页服务、小程序，当我们进入网页、小程序界面，第一次使用就无需注册，直接使用微信授权登录即可，大大提高了使用效率。因为每个人都有微信号，有了微信就可以马上使用第三方服务，这体验不要太好了。而对于我们的服务来说，我们也不需要存储用户的密码，只要存储认证平台返回的唯一ID 和用户信息即可。</p>
<p>以上是使用了 OAuth2 的授权码模式，利用第三方的权威平台实现用户身份的认证。当然了，如果你的公司内部有很多个服务，可以专门提取出一个认证中心，这个认证中心就充当上面所说的权威认证平台的角色，所有的服务都要到这个认证中心做认证。</p>
<p>这样一说，发现没，这其实就是个单点登录的功能。这就是另外一种使用场景，对于多服务的平台，可以使用 OAuth2 实现服务的单点登录，只做一次登录，就可以在多个服务中自由穿行，当然仅限于授权范围内的服务和接口。</p>
<h3 id="实现统一认证功能">实现统一认证功能</h3>
<p>本篇先介绍密码模式实现的单点登录，下一篇再继续说授权码模式。</p>
<p>在微服务横行的今天，谁敢说自己手上没几个微服务。微服务减少了服务间的耦合，同时也在某些方面增加了系统的复杂度，比如说用户认证。假设我们这里实现了一个电商平台，用户看到的就是一个 APP 或者一个 web 站点，实际上背后是由多个独立的服务构成的，比如用户服务、订单服务、产品服务等。用户只要第一次输入用户名、密码完成登录后，一段时间内，都可以任意访问各个页面，比如产品列表页面、我的订单页面、我的关注等页面。</p>
<p>我们可以想象一下，自然能够想到，在请求各个服务、各个接口的时候，一定携带着什么凭证，然后各个服务才知道请求接口的用户是哪个，不然肯定有问题，那其实这里面的凭证简单来说就是一个 Token，标识用户身份的 Token。</p>
<h4 id="系统架构说明">系统架构说明</h4>
<p>认证中心：oauth2-auth-server，OAuth2 主要实现端，Token 的生成、刷新、验证都在认证中心完成。</p>
<p>订单服务：oauth2-client-order-server，微服务之一，接收到请求后会到认证中心验证。</p>
<p>用户服务：oauth2-client-user-server，微服务之二，接收到请求后会到认证中心验证。</p>
<p>客户端：例如 APP 端、web 端 等终端</p>
<p><img src="https://hexo.moonkite.cn/blog/273364-20191023100717630-187909841.png"></p>
<p>上图描述了使用了 OAuth2 的客户端与微服务间的请求过程。大致的过程就是客户端用用户名和密码到认证服务端换取 token，返回给客户端，客户端拿着 token 去各个微服务请求数据接口，一般这个 token 是放到 header 中的。当微服务接到请求后，先要拿着 token 去认证服务端检查 token 的合法性，如果合法，再根据用户所属的角色及具有的权限动态的返回数据。</p>
<h5 id="创建并配置认证服务端">创建并配置认证服务端</h5>
<p>配置最多的就是认证服务端，验证账号、密码，存储 token，检查 token ,刷新 token 等都是认证服务端的工作。</p>
<p><em>1、引入需要的 maven 包</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-web<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-oauth2<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-data-redis<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-actuator<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p><code>spring-cloud-starter-oauth2</code>包含了 <code>spring-cloud-starter-security</code>，所以不用再单独引入了。之所以引入 redis 包，是因为下面会介绍一种用 redis 存储 token 的方式。</p>
<p><em>2、配置好 application.yml</em></p>
<p>将项目基本配置设置好，并加入有关 redis 的配置，稍后会用到。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">auth-server</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">redis</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">database</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">localhost</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">32768</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">1qaz@WSX</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">jedis</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">pool</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">max-active</span><span class="p">:</span><span class="w"> </span><span class="m">8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">max-idle</span><span class="p">:</span><span class="w"> </span><span class="m">8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">min-idle</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="l">100ms</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">6001</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">management</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">endpoint</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">health</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div><p><em>3、spring security 基础配置</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@EnableWebSecurity</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">WebSecurityConfig</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">WebSecurityConfigurerAdapter</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Bean</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">PasswordEncoder</span><span class="w"> </span><span class="nf">passwordEncoder</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">BCryptPasswordEncoder</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Bean</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">AuthenticationManager</span><span class="w"> </span><span class="nf">authenticationManagerBean</span><span class="p">()</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kd">super</span><span class="p">.</span><span class="na">authenticationManagerBean</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 允许匿名访问所有接口 主要是 oauth 接口
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @param http
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @throws Exception
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">protected</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">configure</span><span class="p">(</span><span class="n">HttpSecurity</span><span class="w"> </span><span class="n">http</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">http</span><span class="p">.</span><span class="na">authorizeRequests</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">antMatchers</span><span class="p">(</span><span class="s">&#34;/**&#34;</span><span class="p">).</span><span class="na">permitAll</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>使用<code>@EnableWebSecurity</code>注解修饰，并继承自<code>WebSecurityConfigurerAdapter</code>类。</p>
<p>这个类的重点就是声明 <code>PasswordEncoder</code> 和 <code>AuthenticationManager</code>两个 Bean。稍后会用到。其中 <code>BCryptPasswordEncoder</code>是一个密码加密工具类，它可以实现不可逆的加密，<code>AuthenticationManager</code>是为了实现 OAuth2 的 password 模式必须要指定的授权管理 Bean。</p>
<p><em>4、实现 UserDetailsService</em></p>
<p>如果你之前用过 Security 的话，那肯定对这个类很熟悉，它是实现用户身份验证的一种方式，也是最简单方便的一种。另外还有结合 <code>AuthenticationProvider</code>的方式，有机会讲 Security 的时候再展开来讲吧。</p>
<p><code>UserDetailsService</code>的核心就是 <code>loadUserByUsername</code>方法，它要接收一个字符串参数，也就是传过来的用户名，返回一个 <code>UserDetails</code>对象。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Slf4j</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@Component</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;kiteUserDetailsService&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">KiteUserDetailsService</span><span class="w"> </span><span class="kd">implements</span><span class="w"> </span><span class="n">UserDetailsService</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">PasswordEncoder</span><span class="w"> </span><span class="n">passwordEncoder</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">UserDetails</span><span class="w"> </span><span class="nf">loadUserByUsername</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">username</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">UsernameNotFoundException</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">&#34;usernameis:&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">username</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// 查询数据库操作</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">username</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="s">&#34;admin&#34;</span><span class="p">)){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">UsernameNotFoundException</span><span class="p">(</span><span class="s">&#34;the user is not found&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="k">else</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// 用户角色也应在数据库中获取</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">String</span><span class="w"> </span><span class="n">role</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;ROLE_ADMIN&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">List</span><span class="o">&lt;</span><span class="n">SimpleGrantedAuthority</span><span class="o">&gt;</span><span class="w"> </span><span class="n">authorities</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">authorities</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">SimpleGrantedAuthority</span><span class="p">(</span><span class="n">role</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// 线上环境应该通过用户名查询数据库获取加密后的密码</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">String</span><span class="w"> </span><span class="n">password</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">passwordEncoder</span><span class="p">.</span><span class="na">encode</span><span class="p">(</span><span class="s">&#34;123456&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">org</span><span class="p">.</span><span class="na">springframework</span><span class="p">.</span><span class="na">security</span><span class="p">.</span><span class="na">core</span><span class="p">.</span><span class="na">userdetails</span><span class="p">.</span><span class="na">User</span><span class="p">(</span><span class="n">username</span><span class="p">,</span><span class="n">password</span><span class="p">,</span><span class="w"> </span><span class="n">authorities</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>这里为了做演示，把用户名、密码和所属角色都写在代码里了，正式环境中，这里应该是从数据库或者其他地方根据用户名将加密后的密码及所属角色查出来的。账号 admin ，密码 123456，稍后在换取 token 的时候会用到。并且给这个用户设置 &ldquo;ROLE_ADMIN&rdquo; 角色。</p>
<p><em>5、OAuth2 配置文件</em></p>
<p>创建一个配置文件继承自 <code>AuthorizationServerConfigurerAdapter</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Configuration</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableAuthorizationServer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">OAuth2Config</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">AuthorizationServerConfigurerAdapter</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">PasswordEncoder</span><span class="w"> </span><span class="n">passwordEncoder</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">UserDetailsService</span><span class="w"> </span><span class="n">kiteUserDetailsService</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">AuthenticationManager</span><span class="w"> </span><span class="n">authenticationManager</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">TokenStore</span><span class="w"> </span><span class="n">redisTokenStore</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">configure</span><span class="p">(</span><span class="kd">final</span><span class="w"> </span><span class="n">AuthorizationServerEndpointsConfigurer</span><span class="w"> </span><span class="n">endpoints</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">         * redis token 方式
</span></span></span><span class="line"><span class="cl"><span class="cm">         */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">endpoints</span><span class="p">.</span><span class="na">authenticationManager</span><span class="p">(</span><span class="n">authenticationManager</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">userDetailsService</span><span class="p">(</span><span class="n">kiteUserDetailsService</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">tokenStore</span><span class="p">(</span><span class="n">redisTokenStore</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">configure</span><span class="p">(</span><span class="n">ClientDetailsServiceConfigurer</span><span class="w"> </span><span class="n">clients</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">clients</span><span class="p">.</span><span class="na">inMemory</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">withClient</span><span class="p">(</span><span class="s">&#34;order-client&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">secret</span><span class="p">(</span><span class="n">passwordEncoder</span><span class="p">.</span><span class="na">encode</span><span class="p">(</span><span class="s">&#34;order-secret-8888&#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">authorizedGrantTypes</span><span class="p">(</span><span class="s">&#34;refresh_token&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;authorization_code&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;password&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">accessTokenValiditySeconds</span><span class="p">(</span><span class="n">3600</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">scopes</span><span class="p">(</span><span class="s">&#34;all&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">and</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">withClient</span><span class="p">(</span><span class="s">&#34;user-client&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">secret</span><span class="p">(</span><span class="n">passwordEncoder</span><span class="p">.</span><span class="na">encode</span><span class="p">(</span><span class="s">&#34;user-secret-8888&#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">authorizedGrantTypes</span><span class="p">(</span><span class="s">&#34;refresh_token&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;authorization_code&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;password&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">accessTokenValiditySeconds</span><span class="p">(</span><span class="n">3600</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">scopes</span><span class="p">(</span><span class="s">&#34;all&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">configure</span><span class="p">(</span><span class="n">AuthorizationServerSecurityConfigurer</span><span class="w"> </span><span class="n">security</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">security</span><span class="p">.</span><span class="na">allowFormAuthenticationForClients</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">security</span><span class="p">.</span><span class="na">checkTokenAccess</span><span class="p">(</span><span class="s">&#34;isAuthenticated()&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">security</span><span class="p">.</span><span class="na">tokenKeyAccess</span><span class="p">(</span><span class="s">&#34;isAuthenticated()&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>有三个 configure 方法的重写。</p>
<p><code>AuthorizationServerEndpointsConfigurer</code>参数的重写</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">endpoints</span><span class="p">.</span><span class="na">authenticationManager</span><span class="p">(</span><span class="n">authenticationManager</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">userDetailsService</span><span class="p">(</span><span class="n">kiteUserDetailsService</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">tokenStore</span><span class="p">(</span><span class="n">redisTokenStore</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><p><code>authenticationManage() </code> 调用此方法才能支持 password 模式。</p>
<p><code>userDetailsService()</code> 设置用户验证服务。</p>
<p><code>tokenStore()</code> 指定 token 的存储方式。</p>
<p>redisTokenStore Bean 的定义如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Configuration</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">RedisTokenStoreConfig</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">RedisConnectionFactory</span><span class="w"> </span><span class="n">redisConnectionFactory</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Bean</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">TokenStore</span><span class="w"> </span><span class="nf">redisTokenStore</span><span class="w"> </span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">RedisTokenStore</span><span class="p">(</span><span class="n">redisConnectionFactory</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p><code>ClientDetailsServiceConfigurer</code>参数的重写，在这里定义各个端的约束条件。包括</p>
<p>ClientId、Client-Secret：这两个参数对应请求端定义的 cleint-id 和 client-secret</p>
<p>authorizedGrantTypes 可以包括如下几种设置中的一种或多种：</p>
<ul>
<li>authorization_code：授权码类型。</li>
<li>implicit：隐式授权类型。</li>
<li>password：资源所有者（即用户）密码类型。</li>
<li>client_credentials：客户端凭据（客户端ID以及Key）类型。</li>
<li>refresh_token：通过以上授权获得的刷新令牌来获取新的令牌。</li>
</ul>
<p>accessTokenValiditySeconds：token 的有效期</p>
<p>scopes：用来限制客户端访问的权限，在换取的 token 的时候会带上 scope 参数，只有在 scopes 定义内的，才可以正常换取 token。</p>
<p>上面代码中是使用 inMemory 方式存储的，将配置保存到内存中，相当于硬编码了。正式环境下的做法是持久化到数据库中，比如 mysql 中。</p>
<p>具体的做法如下：</p>
<ol>
<li>在数据库中增加表，并插入数据</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-mysql" data-lang="mysql"><span class="line"><span class="cl"><span class="k">create</span><span class="w"> </span><span class="k">table</span><span class="w"> </span><span class="nf">oauth_client_details</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">client_id</span><span class="w"> </span><span class="kt">VARCHAR</span><span class="p">(</span><span class="mi">256</span><span class="p">)</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">resource_ids</span><span class="w"> </span><span class="kt">VARCHAR</span><span class="p">(</span><span class="mi">256</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">client_secret</span><span class="w"> </span><span class="kt">VARCHAR</span><span class="p">(</span><span class="mi">256</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">scope</span><span class="w"> </span><span class="kt">VARCHAR</span><span class="p">(</span><span class="mi">256</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">authorized_grant_types</span><span class="w"> </span><span class="kt">VARCHAR</span><span class="p">(</span><span class="mi">256</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">web_server_redirect_uri</span><span class="w"> </span><span class="kt">VARCHAR</span><span class="p">(</span><span class="mi">256</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">authorities</span><span class="w"> </span><span class="kt">VARCHAR</span><span class="p">(</span><span class="mi">256</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">access_token_validity</span><span class="w"> </span><span class="kt">INTEGER</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">refresh_token_validity</span><span class="w"> </span><span class="kt">INTEGER</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">additional_information</span><span class="w"> </span><span class="kt">VARCHAR</span><span class="p">(</span><span class="mi">4096</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">autoapprove</span><span class="w"> </span><span class="kt">VARCHAR</span><span class="p">(</span><span class="mi">256</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="nf">oauth_client_details</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">(</span><span class="n">client_id</span><span class="p">,</span><span class="w"> </span><span class="n">client_secret</span><span class="p">,</span><span class="w"> </span><span class="n">scope</span><span class="p">,</span><span class="w"> </span><span class="n">authorized_grant_types</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">web_server_redirect_uri</span><span class="p">,</span><span class="w"> </span><span class="n">authorities</span><span class="p">,</span><span class="w"> </span><span class="n">access_token_validity</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">refresh_token_validity</span><span class="p">,</span><span class="w"> </span><span class="n">additional_information</span><span class="p">,</span><span class="w"> </span><span class="n">autoapprove</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">VALUES</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">(</span><span class="s1">&#39;user-client&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;$2a$10$o2l5kA7z.Caekp72h5kU7uqdTDrlamLq.57M1F6ulJln9tRtOJufq&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;all&#39;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s1">&#39;authorization_code,refresh_token,password&#39;</span><span class="p">,</span><span class="w"> </span><span class="no">null</span><span class="p">,</span><span class="w"> </span><span class="no">null</span><span class="p">,</span><span class="w"> </span><span class="mi">3600</span><span class="p">,</span><span class="w"> </span><span class="mi">36000</span><span class="p">,</span><span class="w"> </span><span class="no">null</span><span class="p">,</span><span class="w"> </span><span class="no">true</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="nf">oauth_client_details</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">(</span><span class="n">client_id</span><span class="p">,</span><span class="w"> </span><span class="n">client_secret</span><span class="p">,</span><span class="w"> </span><span class="n">scope</span><span class="p">,</span><span class="w"> </span><span class="n">authorized_grant_types</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">web_server_redirect_uri</span><span class="p">,</span><span class="w"> </span><span class="n">authorities</span><span class="p">,</span><span class="w"> </span><span class="n">access_token_validity</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">refresh_token_validity</span><span class="p">,</span><span class="w"> </span><span class="n">additional_information</span><span class="p">,</span><span class="w"> </span><span class="n">autoapprove</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">VALUES</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">(</span><span class="s1">&#39;order-client&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;$2a$10$GoIOhjqFKVyrabUNcie8d.ADX.qZSxpYbO6YK4L2gsNzlCIxEUDlW&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;all&#39;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s1">&#39;authorization_code,refresh_token,password&#39;</span><span class="p">,</span><span class="w"> </span><span class="no">null</span><span class="p">,</span><span class="w"> </span><span class="no">null</span><span class="p">,</span><span class="w"> </span><span class="mi">3600</span><span class="p">,</span><span class="w"> </span><span class="mi">36000</span><span class="p">,</span><span class="w"> </span><span class="no">null</span><span class="p">,</span><span class="w"> </span><span class="no">true</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><p><em>注意：</em> client_secret 字段不能直接是 secret 的原始值，需要经过加密。因为是用的 <code>BCryptPasswordEncoder</code>，所以最终插入的值应该是经过 <code>BCryptPasswordEncoder.encode()</code>之后的值。</p>
<ol start="2">
<li>然后在配置文件 application.yml 中添加关于数据库的配置</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">datasource</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="l">jdbc:mysql://localhost:3306/spring_cloud?characterEncoding=UTF-8&amp;useSSL=false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">root</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">password</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hikari</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">connection-timeout</span><span class="p">:</span><span class="w"> </span><span class="m">30000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">idle-timeout</span><span class="p">:</span><span class="w"> </span><span class="m">600000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">max-lifetime</span><span class="p">:</span><span class="w"> </span><span class="m">1800000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">maximum-pool-size</span><span class="p">:</span><span class="w"> </span><span class="m">9</span><span class="w">   
</span></span></span></code></pre></div><p>Spring Boot 2.0 之后默认使用 hikari 作为数据库连接池。如果使用其他连接池需要引入相关包，然后对应的增加配置。</p>
<ol start="3">
<li>在 OAuth2 配置类(OAuth2Config)中增加 DataSource 的注入</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">private</span><span class="w"> </span><span class="n">DataSource</span><span class="w"> </span><span class="n">dataSource</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></div><ol start="4">
<li>将 <code>public void configure(ClientDetailsServiceConfigurer clients)</code>重写方法修改为如下：</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">configure</span><span class="p">(</span><span class="n">ClientDetailsServiceConfigurer</span><span class="w"> </span><span class="n">clients</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">JdbcClientDetailsServiceBuilder</span><span class="w"> </span><span class="n">jcsb</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">clients</span><span class="p">.</span><span class="na">jdbc</span><span class="p">(</span><span class="n">dataSource</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">jcsb</span><span class="p">.</span><span class="na">passwordEncoder</span><span class="p">(</span><span class="n">passwordEncoder</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>还有一个重写的方法 <code>public void configure(AuthorizationServerSecurityConfigurer security)</code>，这个方法限制客户端访问认证接口的权限。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">security</span><span class="p">.</span><span class="na">allowFormAuthenticationForClients</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">security</span><span class="p">.</span><span class="na">checkTokenAccess</span><span class="p">(</span><span class="s">&#34;isAuthenticated()&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">security</span><span class="p">.</span><span class="na">tokenKeyAccess</span><span class="p">(</span><span class="s">&#34;isAuthenticated()&#34;</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><p>第一行代码是允许客户端访问 OAuth2 授权接口，否则请求 token 会返回 401。</p>
<p>第二行和第三行分别是允许已授权用户访问 checkToken 接口和获取 token 接口。</p>
<p>完成之后，启动项目，如果你用的是 IDEA 会在下方的 Mapping 窗口中看到 oauth2 相关的 RESTful 接口。
<img src="https://hexo.moonkite.cn/blog/273364-20191023100737268-1008858850.png"></p>
<p>主要有如下几个：</p>
<pre tabindex="0"><code>POST /oauth/authorize  授权码模式认证授权接口
GET/POST /oauth/token  获取 token 的接口
POST  /oauth/check_token  检查 token 合法性接口
</code></pre><h5 id="创建用户客户端项目">创建用户客户端项目</h5>
<p>上面创建完成了认证服务端，下面开始创建一个客户端，对应到我们系统中的业务相关的微服务。我们假设这个微服务项目是管理用户相关数据的，所以叫做用户客户端。</p>
<p><em>1、引用相关的 maven 包</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-web<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-oauth2<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-data-redis<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p><em>2、application.yml 配置文件</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">client-user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">redis</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">database</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">localhost</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">32768</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">1qaz@WSX</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">jedis</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">pool</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">max-active</span><span class="p">:</span><span class="w"> </span><span class="m">8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">max-idle</span><span class="p">:</span><span class="w"> </span><span class="m">8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">min-idle</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="l">100ms</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">6101</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">servlet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">context-path</span><span class="p">:</span><span class="w"> </span><span class="l">/client-user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">security</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">oauth2</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">client-id</span><span class="p">:</span><span class="w"> </span><span class="l">user-client</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">client-secret</span><span class="p">:</span><span class="w"> </span><span class="l">user-secret-8888</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">user-authorization-uri</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:6001/oauth/authorize</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">access-token-uri</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:6001/oauth/token</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">resource</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">user-client</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">user-info-uri</span><span class="p">:</span><span class="w"> </span><span class="l">user-info</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">authorization</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">check-token-access</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:6001/oauth/check_token</span><span class="w">
</span></span></span></code></pre></div><p>上面是常规配置信息以及 redis 配置，重点是下面的 security 的配置，这里的配置稍有不注意就会出现 401 或者其他问题。</p>
<p>client-id、client-secret 要和认证服务中的配置一致，如果是使用 inMemory 还是 jdbc 方式。</p>
<p>user-authorization-uri 是授权码认证方式需要的，下一篇文章再说。</p>
<p>access-token-uri 是密码模式需要用到的获取 token 的接口。</p>
<p>authorization.check-token-access 也是关键信息，当此服务端接收到来自客户端端的请求后，需要拿着请求中的 token 到认证服务端做 token 验证，就是请求的这个接口</p>
<p><em>3、资源配置文件</em></p>
<p>在 OAuth2 的概念里，所有的接口都被称为资源，接口的权限也就是资源的权限，所以 Spring Security OAuth2 中提供了关于资源的注解 <code>@EnableResourceServer</code>，和 <code>@EnableWebSecurity</code>的作用类似。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Configuration</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableResourceServer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableGlobalMethodSecurity</span><span class="p">(</span><span class="n">prePostEnabled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">ResourceServerConfig</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">ResourceServerConfigurerAdapter</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Value</span><span class="p">(</span><span class="s">&#34;${security.oauth2.client.client-id}&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">clientId</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Value</span><span class="p">(</span><span class="s">&#34;${security.oauth2.client.client-secret}&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">secret</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Value</span><span class="p">(</span><span class="s">&#34;${security.oauth2.authorization.check-token-access}&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">checkTokenEndpointUrl</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">RedisConnectionFactory</span><span class="w"> </span><span class="n">redisConnectionFactory</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Bean</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">TokenStore</span><span class="w"> </span><span class="nf">redisTokenStore</span><span class="w"> </span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">RedisTokenStore</span><span class="p">(</span><span class="n">redisConnectionFactory</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Bean</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">RemoteTokenServices</span><span class="w"> </span><span class="nf">tokenService</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">RemoteTokenServices</span><span class="w"> </span><span class="n">tokenService</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">RemoteTokenServices</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">tokenService</span><span class="p">.</span><span class="na">setClientId</span><span class="p">(</span><span class="n">clientId</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">tokenService</span><span class="p">.</span><span class="na">setClientSecret</span><span class="p">(</span><span class="n">secret</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">tokenService</span><span class="p">.</span><span class="na">setCheckTokenEndpointUrl</span><span class="p">(</span><span class="n">checkTokenEndpointUrl</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">tokenService</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">configure</span><span class="p">(</span><span class="n">ResourceServerSecurityConfigurer</span><span class="w"> </span><span class="n">resources</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">resources</span><span class="p">.</span><span class="na">tokenServices</span><span class="p">(</span><span class="n">tokenService</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>因为使用的是 redis 作为 token 的存储，所以需要特殊配置一下叫做 tokenService 的 Bean，通过这个 Bean 才能实现 token 的验证。</p>
<p><em>4、最后，添加一个 RESTful 接口</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Slf4j</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@RestController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">UserController</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;get&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//@PreAuthorize(&#34;hasAuthority(&#39;ROLE_ADMIN&#39;)&#34;)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@PreAuthorize</span><span class="p">(</span><span class="s">&#34;hasAnyRole(&#39;ROLE_ADMIN&#39;)&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">get</span><span class="p">(</span><span class="n">Authentication</span><span class="w"> </span><span class="n">authentication</span><span class="p">){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">//Authentication authentication = SecurityContextHolder.getContext().getAuthentication();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">authentication</span><span class="p">.</span><span class="na">getCredentials</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">OAuth2AuthenticationDetails</span><span class="w"> </span><span class="n">details</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">OAuth2AuthenticationDetails</span><span class="p">)</span><span class="n">authentication</span><span class="p">.</span><span class="na">getDetails</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">token</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">details</span><span class="p">.</span><span class="na">getTokenValue</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">token</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>一个 RESTful 方法，只有当访问用户具有 ROLE_ADMIN 权限时才能访问，否则返回 401 未授权。</p>
<p>通过 Authentication 参数或者 <code>SecurityContextHolder.getContext().getAuthentication() </code> 可以拿到授权信息进行查看。</p>
<h5 id="测试认证功能">测试认证功能</h5>
<p><em>1、启动认证服务端，启动端口为 6001</em></p>
<p><em>2、启动用户服务客户端，启动端口为6101</em></p>
<p><em>3、请求认证服务端获取 token</em></p>
<p>我是用 REST Client 来做访问请求的，请求格式如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-properties" data-lang="properties"><span class="line"><span class="cl"><span class="na">POST</span> <span class="s">http://localhost:6001/oauth/token?grant_type=password&amp;username=admin&amp;password=123456&amp;scope=all</span>
</span></span><span class="line"><span class="cl"><span class="na">Accept</span><span class="o">:</span> <span class="s">*/*</span>
</span></span><span class="line"><span class="cl"><span class="na">Cache-Control</span><span class="o">:</span> <span class="s">no-cache</span>
</span></span><span class="line"><span class="cl"><span class="na">Authorization</span><span class="o">:</span> <span class="s">Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==</span>
</span></span></code></pre></div><p>假设咱们在一个 web 端使用，grant_type 是 password，表明这是使用 OAuth2 的密码模式。</p>
<p>username=admin 和 password=123456 就相当于在 web 端登录界面输入的用户名和密码，我们在认证服务端配置中固定了用户名是 admin 、密码是 123456，而线上环境中则应该通过查询数据库获取。</p>
<p>scope=all 是权限有关的，在认证服务的 OAuthConfig 中指定了 scope 为 all 。</p>
<p>Authorization 要加在请求头中，格式为 Basic 空格 base64(clientId:clientSecret)，这个微服务客户端的 client-id 是 user-client，client-secret 是 user-secret-8888，将这两个值通过冒号连接，并使用 base64 编码(user-client:user-secret-8888)之后的值为 dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==，可以通过 <a href="https://www.sojson.com/base64.html">https://www.sojson.com/base64.html</a> 在线编码获取。</p>
<p><img src="https://hexo.moonkite.cn/blog/273364-20191023100753748-945717226.png"></p>
<p>运行请求后，如果参数都正确的话，获取到的返回内容如下，是一段 json 格式</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;access_token&#34;</span><span class="p">:</span> <span class="s2">&#34;9f958300-5005-46ea-9061-323c9e6c7a4d&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;token_type&#34;</span><span class="p">:</span> <span class="s2">&#34;bearer&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;refresh_token&#34;</span><span class="p">:</span> <span class="s2">&#34;0f5871f5-98f1-405e-848e-80f641bab72e&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;expires_in&#34;</span><span class="p">:</span> <span class="mi">3599</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scope&#34;</span><span class="p">:</span> <span class="s2">&#34;all&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>access_token :  就是之后请求需要带上的 token，也是本次请求的主要目的
token_type：为 bearer，这是 access token 最常用的一种形式
refresh_token：之后可以用这个值来换取新的 token，而不用输入账号密码
expires_in：token 的过期时间(秒)</p>
<p><em>4、用获取到的 token 请求资源接口</em></p>
<p>我们在用户客户端中定义了一个接口 http://localhost:6101/client-user/get，现在就拿着上一步获取的 token 来请求这个接口。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-properties" data-lang="properties"><span class="line"><span class="cl"><span class="na">GET</span> <span class="s">http://localhost:6101/client-user/get</span>
</span></span><span class="line"><span class="cl"><span class="na">Accept</span><span class="o">:</span> <span class="s">*/*</span>
</span></span><span class="line"><span class="cl"><span class="na">Cache-Control</span><span class="o">:</span> <span class="s">no-cache</span>
</span></span><span class="line"><span class="cl"><span class="na">Authorization</span><span class="o">:</span> <span class="s">bearer ce334918-e666-455a-8ecd-8bd680415d84</span>
</span></span></code></pre></div><p>同样需要请求头 Authorization，格式为 bearer + 空格 + token，正常情况下根据接口的逻辑，会把 token 原样返回。</p>
<p><em>5、token 过期后，用 refresh_token 换取 access_token</em></p>
<p>一般都会设置 access_token 的过期时间小于 refresh_token 的过期时间，以便在 access_token 过期后，不用用户再次登录的情况下，获取新的 access_token。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-properties" data-lang="properties"><span class="line"><span class="cl"><span class="c1">### 换取 access_token</span>
</span></span><span class="line"><span class="cl"><span class="na">POST</span> <span class="s">http://localhost:6001/oauth/token?grant_type=refresh_token&amp;refresh_token=706dac10-d48e-4795-8379-efe8307a2282</span>
</span></span><span class="line"><span class="cl"><span class="na">Accept</span><span class="o">:</span> <span class="s">*/*</span>
</span></span><span class="line"><span class="cl"><span class="na">Cache-Control</span><span class="o">:</span> <span class="s">no-cache</span>
</span></span><span class="line"><span class="cl"><span class="na">Authorization</span><span class="o">:</span> <span class="s">Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==</span>
</span></span></code></pre></div><p>grant_type 设置为 refresh_token。</p>
<p>refresh_token 设置为请求 token 时返回的 refresh_token 的值。</p>
<p>请求头加入 Authorization，格式依然是 Basic + 空格 + base64(client-id:client-secret)</p>
<p>请求成功后会返回和请求 token 同样的数据格式。</p>
<h5 id="用-jwt-替换-redistoken">用 JWT 替换 redisToken</h5>
<p>上面 token 的存储用的是 redis 的方案，Spring Security OAuth2 还提供了 jdbc 和 jwt 的支持，jdbc 的暂不考虑，现在来介绍用 JWT 的方式来实现 token 的存储。</p>
<p>用 JWT 的方式就不用把 token 再存储到服务端了，JWT 有自己特殊的加密方式，可以有效的防止数据被篡改，只要不把用户密码等关键信息放到 JWT 里就可以保证安全性。</p>
<p><em>认证服务端改造</em></p>
<p>先把有关 redis 的配置去掉。</p>
<h6 id="添加-jwtconfig-配置类">添加 JwtConfig 配置类</h6>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Configuration</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">JwtTokenConfig</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Bean</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">TokenStore</span><span class="w"> </span><span class="nf">jwtTokenStore</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">JwtTokenStore</span><span class="p">(</span><span class="n">jwtAccessTokenConverter</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Bean</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">JwtAccessTokenConverter</span><span class="w"> </span><span class="nf">jwtAccessTokenConverter</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">JwtAccessTokenConverter</span><span class="w"> </span><span class="n">accessTokenConverter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">JwtAccessTokenConverter</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">accessTokenConverter</span><span class="p">.</span><span class="na">setSigningKey</span><span class="p">(</span><span class="s">&#34;dev&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">accessTokenConverter</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p><code>JwtAccessTokenConverter</code>是为了做 JWT 数据转换，这样做是因为 JWT 有自身独特的数据格式。如果没有了解过 JWT ，可以搜索一下先了解一下。</p>
<h6 id="更改-oauthconfig-配置类">更改 OAuthConfig 配置类</h6>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">private</span><span class="w"> </span><span class="n">TokenStore</span><span class="w"> </span><span class="n">jwtTokenStore</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">private</span><span class="w"> </span><span class="n">JwtAccessTokenConverter</span><span class="w"> </span><span class="n">jwtAccessTokenConverter</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">configure</span><span class="p">(</span><span class="kd">final</span><span class="w"> </span><span class="n">AuthorizationServerEndpointsConfigurer</span><span class="w"> </span><span class="n">endpoints</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">         * 普通 jwt 模式
</span></span></span><span class="line"><span class="cl"><span class="cm">         */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">endpoints</span><span class="p">.</span><span class="na">tokenStore</span><span class="p">(</span><span class="n">jwtTokenStore</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">accessTokenConverter</span><span class="p">(</span><span class="n">jwtAccessTokenConverter</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">userDetailsService</span><span class="p">(</span><span class="n">kiteUserDetailsService</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">                 * 支持 password 模式
</span></span></span><span class="line"><span class="cl"><span class="cm">                 */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">authenticationManager</span><span class="p">(</span><span class="n">authenticationManager</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>注入 JWT 相关的 Bean，然后修改 <code>configure(final AuthorizationServerEndpointsConfigurer endpoints) </code> 方法为 JWT 存储模式。</p>
<p><em>改造用户客户端</em></p>
<h6 id="修改-applicationyml-配置文件">修改 application.yml 配置文件</h6>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">security</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">oauth2</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">client-id</span><span class="p">:</span><span class="w"> </span><span class="l">user-client</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">client-secret</span><span class="p">:</span><span class="w"> </span><span class="l">user-secret-8888</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">user-authorization-uri</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:6001/oauth/authorize</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">access-token-uri</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:6001/oauth/token</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">resource</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">jwt</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">key-uri</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:6001/oauth/token_key</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">key-value</span><span class="p">:</span><span class="w"> </span><span class="l">dev</span><span class="w">
</span></span></span></code></pre></div><p>注意认证服务端 <code>JwtAccessTokenConverter</code>设置的 SigningKey 要和配置文件中的 key-value 相同，不然会导致无法正常解码 JWT ，导致验证不通过。</p>
<h6 id="resourceserverconfig-类的配置">ResourceServerConfig 类的配置</h6>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Configuration</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableResourceServer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableGlobalMethodSecurity</span><span class="p">(</span><span class="n">prePostEnabled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">ResourceServerConfig</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">ResourceServerConfigurerAdapter</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Bean</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">TokenStore</span><span class="w"> </span><span class="nf">jwtTokenStore</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">JwtTokenStore</span><span class="p">(</span><span class="n">jwtAccessTokenConverter</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Bean</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">JwtAccessTokenConverter</span><span class="w"> </span><span class="nf">jwtAccessTokenConverter</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">JwtAccessTokenConverter</span><span class="w"> </span><span class="n">accessTokenConverter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">JwtAccessTokenConverter</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">accessTokenConverter</span><span class="p">.</span><span class="na">setSigningKey</span><span class="p">(</span><span class="s">&#34;dev&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">accessTokenConverter</span><span class="p">.</span><span class="na">setVerifierKey</span><span class="p">(</span><span class="s">&#34;dev&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">accessTokenConverter</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">TokenStore</span><span class="w"> </span><span class="n">jwtTokenStore</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">configure</span><span class="p">(</span><span class="n">ResourceServerSecurityConfigurer</span><span class="w"> </span><span class="n">resources</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">resources</span><span class="p">.</span><span class="na">tokenStore</span><span class="p">(</span><span class="n">jwtTokenStore</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h6 id="运行请求-token-接口的请求">运行请求 token 接口的请求</h6>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-properties" data-lang="properties"><span class="line"><span class="cl"><span class="na">POST</span> <span class="s">http://localhost:6001/oauth/token?grant_type=password&amp;username=admin&amp;password=123456&amp;scope=all</span>
</span></span><span class="line"><span class="cl"><span class="na">Accept</span><span class="o">:</span> <span class="s">*/*</span>
</span></span><span class="line"><span class="cl"><span class="na">Cache-Control</span><span class="o">:</span> <span class="s">no-cache</span>
</span></span><span class="line"><span class="cl"><span class="na">Authorization</span><span class="o">:</span> <span class="s">Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==</span>
</span></span></code></pre></div><p>返回结果如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;access_token&#34;</span><span class="p">:</span> <span class="s2">&#34;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzE3NDM0OTQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiI4Y2NhMjlhZi1lYTc3LTRmZTYtOWZlMS0zMjc0MTVkY2QyMWQiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCIsInNjb3BlIjpbImFsbCJdfQ.0Ik3UwB1xjX2le5luEdtVAI_MEyu_OloRRYtPOvtvwM&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;token_type&#34;</span><span class="p">:</span> <span class="s2">&#34;bearer&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;refresh_token&#34;</span><span class="p">:</span> <span class="s2">&#34;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI4Y2NhMjlhZi1lYTc3LTRmZTYtOWZlMS0zMjc0MTVkY2QyMWQiLCJleHAiOjE1NzE3NzU4OTQsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iXSwianRpIjoiZjdkMjg4NDUtMmU2ZC00ZmRjLTg1OGYtMWNiY2RlNzI1ZmMyIiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQifQ.vk_msYtbrAr93h5sK4wy6EC2_wRD_cD_UBS8O6eRziw&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;expires_in&#34;</span><span class="p">:</span> <span class="mi">3599</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scope&#34;</span><span class="p">:</span> <span class="s2">&#34;all&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;jti&#34;</span><span class="p">:</span> <span class="s2">&#34;8cca29af-ea77-4fe6-9fe1-327415dcd21d&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>我们已经看到返回的 token 是 JWT 格式了，到 JWT 在线解码网站 <a href="https://jwt.io/">https://jwt.io/</a> 或者 <a href="http://jwt.calebb.net/">http://jwt.calebb.net/</a>将 token 解码看一下</p>
<p><img src="https://hexo.moonkite.cn/blog/273364-20191023100809148-1936501614.png"></p>
<p>看到了没，user_name、client_id 等信息都在其中。</p>
<h6 id="拿着返回的-token-请求用户客户端接口">拿着返回的 token 请求用户客户端接口</h6>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-properties" data-lang="properties"><span class="line"><span class="cl"><span class="na">GET</span> <span class="s">http://localhost:6101/client-user/get</span>
</span></span><span class="line"><span class="cl"><span class="na">Accept</span><span class="o">:</span> <span class="s">*/*</span>
</span></span><span class="line"><span class="cl"><span class="na">Cache-Control</span><span class="o">:</span> <span class="s">no-cache</span>
</span></span><span class="line"><span class="cl"><span class="na">Authorization</span><span class="o">:</span> <span class="s">bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzE3NDM0OTQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiI4Y2NhMjlhZi1lYTc3LTRmZTYtOWZlMS0zMjc0MTVkY2QyMWQiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCIsInNjb3BlIjpbImFsbCJdfQ.0Ik3UwB1xjX2le5luEdtVAI_MEyu_OloRRYtPOvtvwM</span>
</span></span></code></pre></div><p><em>增强 JWT</em></p>
<p>如果我想在 JWT 中加入额外的字段(比方说用户的其他信息)怎么办呢，当然可以。spring security oauth2 提供了 <code>TokenEnhancer</code> 增强器。其实不光 JWT ，RedisToken 的方式同样可以。</p>
<h6 id="声明一个增强器">声明一个增强器</h6>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">JWTokenEnhancer</span><span class="w"> </span><span class="kd">implements</span><span class="w"> </span><span class="n">TokenEnhancer</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">OAuth2AccessToken</span><span class="w"> </span><span class="nf">enhance</span><span class="p">(</span><span class="n">OAuth2AccessToken</span><span class="w"> </span><span class="n">oAuth2AccessToken</span><span class="p">,</span><span class="w"> </span><span class="n">OAuth2Authentication</span><span class="w"> </span><span class="n">oAuth2Authentication</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"> </span><span class="n">Object</span><span class="o">&gt;</span><span class="w"> </span><span class="n">info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">HashMap</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">info</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="s">&#34;jwt-ext&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;JWT 扩展信息&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">((</span><span class="n">DefaultOAuth2AccessToken</span><span class="p">)</span><span class="w"> </span><span class="n">oAuth2AccessToken</span><span class="p">).</span><span class="na">setAdditionalInformation</span><span class="p">(</span><span class="n">info</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">oAuth2AccessToken</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>通过 oAuth2Authentication 可以拿到用户名等信息，通过这些我们可以在这里查询数据库或者缓存获取更多的信息，而这些信息都可以作为 JWT 扩展信息加入其中。</p>
<h6 id="oauthconfig-配置类修改">OAuthConfig 配置类修改</h6>
<p>注入增强器</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">private</span><span class="w"> </span><span class="n">TokenEnhancer</span><span class="w"> </span><span class="n">jwtTokenEnhancer</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@Bean</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="n">TokenEnhancer</span><span class="w"> </span><span class="nf">jwtTokenEnhancer</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">JWTokenEnhancer</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>修改 <code>configure(final AuthorizationServerEndpointsConfigurer endpoints) </code>方法</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">configure</span><span class="p">(</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">AuthorizationServerEndpointsConfigurer</span><span class="w"> </span><span class="n">endpoints</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * jwt 增强模式
</span></span></span><span class="line"><span class="cl"><span class="cm">	 */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">TokenEnhancerChain</span><span class="w">	</span><span class="n">enhancerChain</span><span class="w">	</span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">TokenEnhancerChain</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">List</span><span class="o">&lt;</span><span class="n">TokenEnhancer</span><span class="o">&gt;</span><span class="w">	</span><span class="n">enhancerList</span><span class="w">	</span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">enhancerList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="w"> </span><span class="n">jwtTokenEnhancer</span><span class="w"> </span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">enhancerList</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="w"> </span><span class="n">jwtAccessTokenConverter</span><span class="w"> </span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">enhancerChain</span><span class="p">.</span><span class="na">setTokenEnhancers</span><span class="p">(</span><span class="w"> </span><span class="n">enhancerList</span><span class="w"> </span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">endpoints</span><span class="p">.</span><span class="na">tokenStore</span><span class="p">(</span><span class="w"> </span><span class="n">jwtTokenStore</span><span class="w"> </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">.</span><span class="na">userDetailsService</span><span class="p">(</span><span class="w"> </span><span class="n">kiteUserDetailsService</span><span class="w"> </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * 支持 password 模式
</span></span></span><span class="line"><span class="cl"><span class="cm">	 */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">.</span><span class="na">authenticationManager</span><span class="p">(</span><span class="w"> </span><span class="n">authenticationManager</span><span class="w"> </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">.</span><span class="na">tokenEnhancer</span><span class="p">(</span><span class="w"> </span><span class="n">enhancerChain</span><span class="w"> </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">.</span><span class="na">accessTokenConverter</span><span class="p">(</span><span class="w"> </span><span class="n">jwtAccessTokenConverter</span><span class="w"> </span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h6 id="再次请求-token-返回内容中多了个刚刚加入的-jwt-ext-字段">再次请求 token ，返回内容中多了个刚刚加入的 jwt-ext 字段</h6>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;access_token&#34;</span><span class="p">:</span> <span class="s2">&#34;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTU3MTc0NTE3OCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJhNDU1MWQ5ZS1iN2VkLTQ3NTktYjJmMS1mMGI5YjIxY2E0MmMiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCJ9.5j4hNsVpktG2iKxNqR-q1rfcnhlyV3M6HUBx5cd6PiQ&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;token_type&#34;</span><span class="p">:</span> <span class="s2">&#34;bearer&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;refresh_token&#34;</span><span class="p">:</span> <span class="s2">&#34;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImE0NTUxZDllLWI3ZWQtNDc1OS1iMmYxLWYwYjliMjFjYTQyYyIsImV4cCI6MTU3MTc3NzU3OCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJmNTI3ODJlOS0wOGRjLTQ2NGUtYmJhYy03OTMwNzYwYmZiZjciLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCJ9.UQMf140CG8U0eWh08nGlctpIye9iJ7p2i6NYHkGAwhY&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;expires_in&#34;</span><span class="p">:</span> <span class="mi">3599</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scope&#34;</span><span class="p">:</span> <span class="s2">&#34;all&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;jwt-ext&#34;</span><span class="p">:</span> <span class="s2">&#34;JWT 扩展信息&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;jti&#34;</span><span class="p">:</span> <span class="s2">&#34;a4551d9e-b7ed-4759-b2f1-f0b9b21ca42c&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><em>用户客户端解析 JWT 数据</em></p>
<p>我们如果在 JWT 中加入了额外信息，这些信息我们可能会用到，而在接收到 JWT 格式的 token 之后，用户客户端要把 JWT 解析出来。</p>
<h6 id="引入-jwt-包">引入 JWT 包</h6>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>io.jsonwebtoken<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>jjwt<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;version&gt;</span>0.9.1<span class="nt">&lt;/version&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><h6 id="加一个-restful-接口在其中解析-jwt">加一个 RESTful 接口，在其中解析 JWT</h6>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;jwt&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@PreAuthorize</span><span class="p">(</span><span class="s">&#34;hasAnyRole(&#39;ROLE_ADMIN&#39;)&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">jwtParser</span><span class="p">(</span><span class="n">Authentication</span><span class="w"> </span><span class="n">authentication</span><span class="p">){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">authentication</span><span class="p">.</span><span class="na">getCredentials</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">OAuth2AuthenticationDetails</span><span class="w"> </span><span class="n">details</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">OAuth2AuthenticationDetails</span><span class="p">)</span><span class="n">authentication</span><span class="p">.</span><span class="na">getDetails</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="n">jwtToken</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">details</span><span class="p">.</span><span class="na">getTokenValue</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Claims</span><span class="w"> </span><span class="n">claims</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Jwts</span><span class="p">.</span><span class="na">parser</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">setSigningKey</span><span class="p">(</span><span class="s">&#34;dev&#34;</span><span class="p">.</span><span class="na">getBytes</span><span class="p">(</span><span class="n">StandardCharsets</span><span class="p">.</span><span class="na">UTF_8</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">parseClaimsJws</span><span class="p">(</span><span class="n">jwtToken</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">getBody</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">claims</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>同样注意其中签名的设置要与认证服务端相同。</p>
<h6 id="用上一步的-token-请求上面的接口">用上一步的 token 请求上面的接口</h6>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-properties" data-lang="properties"><span class="line"><span class="cl"><span class="c1">### 解析 jwt</span>
</span></span><span class="line"><span class="cl"><span class="na">GET</span> <span class="s">http://localhost:6101/client-user/jwt</span>
</span></span><span class="line"><span class="cl"><span class="na">Accept</span><span class="o">:</span> <span class="s">*/*</span>
</span></span><span class="line"><span class="cl"><span class="na">Cache-Control</span><span class="o">:</span> <span class="s">no-cache</span>
</span></span><span class="line"><span class="cl"><span class="na">Authorization</span><span class="o">:</span> <span class="s">bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTU3MTc0NTE3OCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJhNDU1MWQ5ZS1iN2VkLTQ3NTktYjJmMS1mMGI5YjIxY2E0MmMiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCJ9.5j4hNsVpktG2iKxNqR-q1rfcnhlyV3M6HUBx5cd6PiQ</span>
</span></span></code></pre></div><p>返回内容如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;user_name&#34;</span><span class="p">:</span> <span class="s2">&#34;admin&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;jwt-ext&#34;</span><span class="p">:</span> <span class="s2">&#34;JWT 扩展信息&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scope&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;all&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;exp&#34;</span><span class="p">:</span> <span class="mi">1571745178</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;authorities&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;ROLE_ADMIN&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;jti&#34;</span><span class="p">:</span> <span class="s2">&#34;a4551d9e-b7ed-4759-b2f1-f0b9b21ca42c&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;client_id&#34;</span><span class="p">:</span> <span class="s2">&#34;user-client&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>以上就是 password 模式的完整过程，源码放到了 github 上，有需要的可以去看一下。</p>
<p><a href="https://github.com/huzhicheng/spring-cloud-study/tree/master/oauth2">源码地址</a></p>
]]></content:encoded></item><item><title>9. 微信授权就是这个原理，Spring Cloud OAuth2 授权码模式</title><link>https://www.showcodeba.com/category/SpringCloud/9-%E5%BE%AE%E4%BF%A1%E6%8E%88%E6%9D%83%E5%B0%B1%E6%98%AF%E8%BF%99%E4%B8%AA%E5%8E%9F%E7%90%86Spring-Cloud-OAuth2-%E6%8E%88%E6%9D%83%E7%A0%81%E6%A8%A1%E5%BC%8F/</link><pubDate>Fri, 02 Jun 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/SpringCloud/9-%E5%BE%AE%E4%BF%A1%E6%8E%88%E6%9D%83%E5%B0%B1%E6%98%AF%E8%BF%99%E4%B8%AA%E5%8E%9F%E7%90%86Spring-Cloud-OAuth2-%E6%8E%88%E6%9D%83%E7%A0%81%E6%A8%A1%E5%BC%8F/</guid><description>上一篇文章Spring Cloud OAuth2 实现单点登录介绍了使用 password 模式进行身份认证和单点登录。本篇介绍 Spring Cloud OAuth2 的另外一种授权模式-授权码模式。 授权码模式的认证</description><content:encoded><![CDATA[<blockquote>
<p>上一篇文章<a href="https://www.moonkite.cn/category/SpringCloud/8-Spring-Cloud-OAuth2-%E5%AE%9E%E7%8E%B0%E7%94%A8%E6%88%B7%E8%AE%A4%E8%AF%81%E5%8F%8A%E5%8D%95%E7%82%B9%E7%99%BB%E5%BD%95/">Spring Cloud OAuth2 实现单点登录</a>介绍了使用 password 模式进行身份认证和单点登录。本篇介绍 Spring Cloud OAuth2 的另外一种授权模式-授权码模式。</p>
</blockquote>
<p>授权码模式的认证过程是这样的：</p>
<p>1、用户客户端请求认证服务器的认证接口，并附上回调地址；</p>
<p>2、认证服务接口接收到认证请求后调整到自身的登录界面；</p>
<p>3、用户输入用户名和密码，点击确认，跳转到授权、拒绝提示页面(也可省略)；</p>
<p>4、用户点击授权或者默认授权后，跳转到微服务客户端的回调地址，并传入参数 code；</p>
<p>5、回调地址一般是一个 RESTful 接口，此接口拿到 code 参数后，再次请求认证服务器的 token 获取接口，用来换取 access_token 等信息；</p>
<p>6、获取到 access_token 后，拿着 token 去请求各个微服务客户端的接口。</p>
<p>注意上面所说的用户客户端可以理解为浏览器、app 端，微服务客户端就是我们系统中的例如订单服务、用户服务等微服务，认证服务端就是用来做认证授权的服务，相对于认证服务端来说，各个业务微服务也可以称作是它的客户端。</p>
<h3 id="认证服务端配置">认证服务端配置</h3>
<p>认证服务端继续用上一篇文章的配置，代码不需要任何改变，只需要在数据库里加一条记录，来支持新加的微服务客户端的认证</p>
<p>我们要创建的客户端的 client-id 为 code-client，client-secret 为 code-secret-8888，但是同样需要加密，可以用如下代码获取：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">BCryptPasswordEncoder</span><span class="p">().</span><span class="na">encode</span><span class="p">(</span><span class="s">&#34;code-secret-8888&#34;</span><span class="p">));</span><span class="w">
</span></span></span></code></pre></div><p>除了以上这两个参数，要将 authorized_grant_types 设置为 authorization_code,refresh_token，web_server_redirect_uri 设置为回调地址，稍后微服务客户端会创建这个接口。</p>
<p>然后将这条记录组织好插入数据库中。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-mysql" data-lang="mysql"><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="nf">oauth_client_details</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">(</span><span class="n">client_id</span><span class="p">,</span><span class="w"> </span><span class="n">client_secret</span><span class="p">,</span><span class="w"> </span><span class="n">scope</span><span class="p">,</span><span class="w"> </span><span class="n">authorized_grant_types</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">web_server_redirect_uri</span><span class="p">,</span><span class="w"> </span><span class="n">authorities</span><span class="p">,</span><span class="w"> </span><span class="n">access_token_validity</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">refresh_token_validity</span><span class="p">,</span><span class="w"> </span><span class="n">additional_information</span><span class="p">,</span><span class="w"> </span><span class="n">autoapprove</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">VALUES</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">(</span><span class="s1">&#39;code-client&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;$2a$10$jENDQZRtqqdr6sXGQK.L0OBADGIpyhtaRfaRDTeLKI76I/Ir1FDn6&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;all&#39;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s1">&#39;authorization_code,refresh_token&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;http://localhost:6102/client-authcode/login&#39;</span><span class="p">,</span><span class="w"> </span><span class="no">null</span><span class="p">,</span><span class="w"> </span><span class="mi">3600</span><span class="p">,</span><span class="w"> </span><span class="mi">36000</span><span class="p">,</span><span class="w"> </span><span class="no">null</span><span class="p">,</span><span class="w"> </span><span class="no">true</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><h3 id="创建授权模式的微服务">创建授权模式的微服务</h3>
<p><strong>引入 maven 包</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-web<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.cloud<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-oauth2<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-data-redis<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>io.jsonwebtoken<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>jjwt<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;version&gt;</span>0.9.1<span class="nt">&lt;/version&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>com.squareup.okhttp3<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>okhttp<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;version&gt;</span>3.14.2<span class="nt">&lt;/version&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-thymeleaf<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>引入 okhttp 和 thymeleaf 是因为要做一个简单的页面并模拟正常的认证过程。</p>
<p><strong>配置文件 application.yml</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">application</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">client-authcode</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">6102</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">servlet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">context-path</span><span class="p">:</span><span class="w"> </span><span class="l">/client-authcode</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">security</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">oauth2</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">client-id</span><span class="p">:</span><span class="w"> </span><span class="l">code-client</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">client-secret</span><span class="p">:</span><span class="w"> </span><span class="l">code-secret-8888</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">user-authorization-uri</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:6001/oauth/authorize</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">access-token-uri</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:6001/oauth/token</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">resource</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">jwt</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">key-uri</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:6001/oauth/token_key</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">key-value</span><span class="p">:</span><span class="w"> </span><span class="l">dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">authorization</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">check-token-access</span><span class="p">:</span><span class="w"> </span><span class="l">http://localhost:6001/oauth/check_token</span><span class="w">
</span></span></span></code></pre></div><p>**创建 resourceConfig **</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Configuration</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableResourceServer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@EnableGlobalMethodSecurity</span><span class="p">(</span><span class="n">prePostEnabled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">ResourceServerConfig</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">ResourceServerConfigurerAdapter</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Bean</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">TokenStore</span><span class="w"> </span><span class="nf">jwtTokenStore</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">JwtTokenStore</span><span class="p">(</span><span class="n">jwtAccessTokenConverter</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Bean</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">JwtAccessTokenConverter</span><span class="w"> </span><span class="nf">jwtAccessTokenConverter</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">JwtAccessTokenConverter</span><span class="w"> </span><span class="n">accessTokenConverter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">JwtAccessTokenConverter</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">accessTokenConverter</span><span class="p">.</span><span class="na">setSigningKey</span><span class="p">(</span><span class="s">&#34;dev&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">accessTokenConverter</span><span class="p">.</span><span class="na">setVerifierKey</span><span class="p">(</span><span class="s">&#34;dev&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">accessTokenConverter</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Autowired</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="n">TokenStore</span><span class="w"> </span><span class="n">jwtTokenStore</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">configure</span><span class="p">(</span><span class="n">ResourceServerSecurityConfigurer</span><span class="w"> </span><span class="n">resources</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">resources</span><span class="p">.</span><span class="na">tokenStore</span><span class="p">(</span><span class="n">jwtTokenStore</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">configure</span><span class="p">(</span><span class="n">HttpSecurity</span><span class="w"> </span><span class="n">http</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">http</span><span class="p">.</span><span class="na">authorizeRequests</span><span class="p">().</span><span class="na">antMatchers</span><span class="p">(</span><span class="s">&#34;/login&#34;</span><span class="p">).</span><span class="na">permitAll</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>使用 jwt 作为 token 的存储，注意允许  <code>/login</code> 接口无授权访问，这个地址是认证的回调地址，会返回 code 参数。</p>
<p><strong>创建 application.java启动类</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@SpringBootApplication</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">Application</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">SpringApplication</span><span class="p">.</span><span class="na">run</span><span class="p">(</span><span class="n">Application</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>到这步可以先停一下了。我们把认证服务端和刚刚创建的认证客户端启动起来，就可以手工测试一下了。回调接口不是还没创建呢吗，没关系，我们权当那个地址现在就是为了接收 code 参数的。
**1、**在浏览器访问 /oauth/authorize 授权接口，接口地址为：</p>
<pre tabindex="0"><code>http://localhost:6001/oauth/authorize?client_id=code-client&amp;response_type=code&amp;redirect_uri=http://localhost:6102/client-authcode/login 
</code></pre><p>注意 response_type 参数设置为 code，redirect_uri 设置为数据库中插入的回调地址。</p>
<p>**2、**输入上面地址后，会自动跳转到认证服务端的登录页面，输入用户名、密码，这里用户名是 admin，密码是 123456
<img src="https://hexo.moonkite.cn/blog/273364-20191107100706180-194452156.png"></p>
<p>**3、**点击确定后，来到授权确认页面，页面上有 Authorize 和 Deny (授权和拒绝)两个按钮。可通过将 autoapprove 字段设置为 0 来取消此页面的展示，默认直接同意授权。
<img src="https://hexo.moonkite.cn/blog/273364-20191107100718252-1368402091.png"></p>
<p>**4、**点击同意授权后，跳转到了回调地址，虽然是 404 ，但是我们只是为了拿到 code 参数，注意地址后面的 code 参数。
<img src="https://hexo.moonkite.cn/blog/273364-20191107100737632-1410280038.png"></p>
<p>**5、**拿到这个 code 参数是为了向认证服务器 /oauth/token 接口请求 access_token ，继续用 REST Client 发送请求，同样的，你也可以用 postman 等工具测试。</p>
<p>注意 grant_type 参数设置为 authorization_code，code 就是上一步回调地址中加上的，redirect_uri 仍然要带上，回作为验证条件，如果不带或者与前面设置的不一致，会出现错误。</p>
<p>请求头 Authorization ，仍然是 Basic + 空格 + base64(client_id:client_secret)，可以通过 <a href="https://www.sojson.com/base64.html">https://www.sojson.com/base64.html</a> 网站在线做 base64 编码。</p>
<p>code-client:code-secret-8888 通过 base64 编码后结果为 Y29kZS1jbGllbnQ6Y29kZS1zZWNyZXQtODg4OA==</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-properties" data-lang="properties"><span class="line"><span class="cl"><span class="na">POST</span> <span class="s">http://localhost:6001/oauth/token?grant_type=authorization_code&amp;client=code-client&amp;code=BbCE34&amp;redirect_uri=http://localhost:6102/client-authcode/login</span>
</span></span><span class="line"><span class="cl"><span class="na">Accept</span><span class="o">:</span> <span class="s">*/*</span>
</span></span><span class="line"><span class="cl"><span class="na">Cache-Control</span><span class="o">:</span> <span class="s">no-cache</span>
</span></span><span class="line"><span class="cl"><span class="na">Authorization</span><span class="o">:</span> <span class="s">Basic Y29kZS1jbGllbnQ6Y29kZS1zZWNyZXQtODg4OA==</span>
</span></span></code></pre></div><p>发送请求后，返回的 json 内容如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;access_token&#34;</span><span class="p">:</span> <span class="s2">&#34;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTU3MjYwMTMzMiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiI2OWRmY2M4Yy1iZmZiLTRiNDItYTZhZi1hN2IzZWUyZjI1ZTMiLCJjbGllbnRfaWQiOiJjb2RlLWNsaWVudCJ9.WlgGnBkNdg2PwKqjbZWo6QmUmq0QluZLgIWJXaZahSU&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;token_type&#34;</span><span class="p">:</span> <span class="s2">&#34;bearer&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;refresh_token&#34;</span><span class="p">:</span> <span class="s2">&#34;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6IjY5ZGZjYzhjLWJmZmItNGI0Mi1hNmFmLWE3YjNlZTJmMjVlMyIsImV4cCI6MTU3MjYzMzczMiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJkNzk2OWRhMS04NTg4LTQ2YzMtYjdlNS1jMGM5NzcxNTM5Y2YiLCJjbGllbnRfaWQiOiJjb2RlLWNsaWVudCJ9.TEz0pQOhST9-ozdoJWm6cf1SoWvPC6W-5JW9yjZJXek&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;expires_in&#34;</span><span class="p">:</span> <span class="mi">3599</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scope&#34;</span><span class="p">:</span> <span class="s2">&#34;all&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;jwt-ext&#34;</span><span class="p">:</span> <span class="s2">&#34;JWT 扩展信息&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;jti&#34;</span><span class="p">:</span> <span class="s2">&#34;69dfcc8c-bffb-4b42-a6af-a7b3ee2f25e3&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>和上一篇文章 password 模式拿到的 token 内容是一致的，接下来的请求都需要带上 access_token 。</p>
<p>**6、**把获取到的 access_token 代入到下面的请求中 ${access_token} 的位置，就可以请求微服务中的需要授权访问的接口了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-properties" data-lang="properties"><span class="line"><span class="cl"><span class="na">GET</span> <span class="s">http://localhost:6102/client-authcode/get</span>
</span></span><span class="line"><span class="cl"><span class="na">Accept</span><span class="o">:</span> <span class="s">*/*</span>
</span></span><span class="line"><span class="cl"><span class="na">Cache-Control</span><span class="o">:</span> <span class="s">no-cache</span>
</span></span><span class="line"><span class="cl"><span class="na">Authorization</span><span class="o">:</span> <span class="s">bearer ${access_token}</span>
</span></span></code></pre></div><p>接口内容如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@org.springframework.web.bind.annotation.ResponseBody</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;get&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@PreAuthorize</span><span class="p">(</span><span class="s">&#34;hasAnyRole(&#39;ROLE_ADMIN&#39;)&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">get</span><span class="p">(</span><span class="n">Authentication</span><span class="w"> </span><span class="n">authentication</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//Authentication authentication = SecurityContextHolder.getContext().getAuthentication();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">authentication</span><span class="p">.</span><span class="na">getCredentials</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">OAuth2AuthenticationDetails</span><span class="w"> </span><span class="n">details</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">OAuth2AuthenticationDetails</span><span class="p">)</span><span class="w"> </span><span class="n">authentication</span><span class="p">.</span><span class="na">getDetails</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="n">token</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">details</span><span class="p">.</span><span class="na">getTokenValue</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">token</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>经过以上的手工测试，证明此过程是通的，但是还没有达到自动化。如果你集成过微信登录，那你一定知道我们在回调地址中做了什么，拿到返回的 code 参数去 token 接口换取 access_token 对不对，没错，思路都是一样的，我们的回调接口中同样要拿 code 去换取 access_token。</p>
<p>为此，我做了一个简单的页面，并且在回调接口中请求获取 token 的接口。</p>
<p><strong>创建简单的登录页面</strong></p>
<p>在 resources 目录下创建 templates 目录，用来存放 thymeleaf 的模板，不做样式，只做最简单的演示，创建 index.html 模板，内容如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="cp">&lt;!DOCTYPE html&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">html</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;en&#34;</span> <span class="na">xmlns:th</span><span class="o">=</span><span class="s">&#34;http://www.thymeleaf.org&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&#34;UTF-8&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>古时的风筝-OAuth2 Client<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;http://localhost:6001/oauth/authorize?client_id=code-client&amp;response_type=code&amp;redirect_uri=http://localhost:6102/client-authcode/login&#34;</span><span class="p">&gt;</span>登录<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">span</span> <span class="na">th:text</span><span class="o">=</span><span class="s">&#34;&#39;当前认证用户：&#39; + ${username}&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">span</span> <span class="na">th:text</span><span class="o">=</span><span class="s">&#34;${accessToken}&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span></code></pre></div><p><strong>回调接口及其他接口</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Slf4j</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nd">@Controller</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">CodeClientController</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 用来展示index.html 模板
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @return
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span><span class="w">  	
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;index&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">index</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;index&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;login&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">login</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">code</span><span class="p">,</span><span class="n">Model</span><span class="w"> </span><span class="n">model</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">tokenUrl</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;http://localhost:6001/oauth/token&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">OkHttpClient</span><span class="w"> </span><span class="n">httpClient</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">OkHttpClient</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">RequestBody</span><span class="w"> </span><span class="n">body</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">FormBody</span><span class="p">.</span><span class="na">Builder</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;grant_type&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;authorization_code&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;client&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;code-client&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;redirect_uri&#34;</span><span class="p">,</span><span class="s">&#34;http://localhost:6102/client-authcode/login&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">&#34;code&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">code</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">build</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Request</span><span class="w"> </span><span class="n">request</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">Request</span><span class="p">.</span><span class="na">Builder</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">url</span><span class="p">(</span><span class="n">tokenUrl</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">post</span><span class="p">(</span><span class="n">body</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">addHeader</span><span class="p">(</span><span class="s">&#34;Authorization&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Basic Y29kZS1jbGllbnQ6Y29kZS1zZWNyZXQtODg4OA==&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">build</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">Response</span><span class="w"> </span><span class="n">response</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">httpClient</span><span class="p">.</span><span class="na">newCall</span><span class="p">(</span><span class="n">request</span><span class="p">).</span><span class="na">execute</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">String</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">response</span><span class="p">.</span><span class="na">body</span><span class="p">().</span><span class="na">string</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">ObjectMapper</span><span class="w"> </span><span class="n">objectMapper</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">ObjectMapper</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">Map</span><span class="w"> </span><span class="n">tokenMap</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">objectMapper</span><span class="p">.</span><span class="na">readValue</span><span class="p">(</span><span class="n">result</span><span class="p">,</span><span class="n">Map</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">String</span><span class="w"> </span><span class="n">accessToken</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">tokenMap</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">&#34;access_token&#34;</span><span class="p">).</span><span class="na">toString</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">Claims</span><span class="w"> </span><span class="n">claims</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Jwts</span><span class="p">.</span><span class="na">parser</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">.</span><span class="na">setSigningKey</span><span class="p">(</span><span class="s">&#34;dev&#34;</span><span class="p">.</span><span class="na">getBytes</span><span class="p">(</span><span class="n">StandardCharsets</span><span class="p">.</span><span class="na">UTF_8</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">.</span><span class="na">parseClaimsJws</span><span class="p">(</span><span class="n">accessToken</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">.</span><span class="na">getBody</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">String</span><span class="w"> </span><span class="n">userName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">claims</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">&#34;user_name&#34;</span><span class="p">).</span><span class="na">toString</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">model</span><span class="p">.</span><span class="na">addAttribute</span><span class="p">(</span><span class="s">&#34;username&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">userName</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">model</span><span class="p">.</span><span class="na">addAttribute</span><span class="p">(</span><span class="s">&#34;accessToken&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">result</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;index&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="n">Exception</span><span class="w"> </span><span class="n">e</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kc">null</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@org.springframework.web.bind.annotation.ResponseBody</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@GetMapping</span><span class="p">(</span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;get&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@PreAuthorize</span><span class="p">(</span><span class="s">&#34;hasAnyRole(&#39;ROLE_ADMIN&#39;)&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">get</span><span class="p">(</span><span class="n">Authentication</span><span class="w"> </span><span class="n">authentication</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">//Authentication authentication = SecurityContextHolder.getContext().getAuthentication();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">authentication</span><span class="p">.</span><span class="na">getCredentials</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">OAuth2AuthenticationDetails</span><span class="w"> </span><span class="n">details</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">OAuth2AuthenticationDetails</span><span class="p">)</span><span class="w"> </span><span class="n">authentication</span><span class="p">.</span><span class="na">getDetails</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">token</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">details</span><span class="p">.</span><span class="na">getTokenValue</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">token</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>其中 index() 方法是为了展示 thymeleaf 模板，login 方法就是回调接口，这里用了 okhttp3 用作接口请求，请求认证服务端的 /oauth/token 接口来换取 access_token，只是把我们手工测试的步骤自动化了。</p>
<p><strong>访问 index.html 页面</strong></p>
<p>我们假设这个页面就是一个网站的首页，未登录的用户会在网站上看到登录按钮，我们访问这个页面：<a href="http://localhost:6102/client-authcode/index">http://localhost:6102/client-authcode/index</a>，看到的页面是这样的
<img src="https://hexo.moonkite.cn/blog/273364-20191107100802479-1544165549.png"></p>
<p>接下来，点击登录按钮，通过上面的模板代码看出，点击后其实就是跳转到了我们手工测试第一步访问的那个地址，之后的操作和上面手工测试的是一致的，输入用户名密码、点击同意授权。</p>
<p>接下来，页面跳转回回调地址&lt;http://localhost:6102/client-authcode/login?code=xxx 的时候，login 方法拿到 code 参数，开始构造 post 请求体，并把 Authorization 加入请求头，然后请求 oauth/token 接口，最后将拿到的 token 和 通过 token 解析后的 username 返回给前端，最后呈现的效果如下：
<img src="https://hexo.moonkite.cn/blog/273364-20191107100813527-883457281.png"></p>
<p>最后，拿到 token 后的客户端，就可以将 token 加入到请求头后，去访问需要授权的接口了。</p>
<p>结合上一篇文章，我们就实现了 password 和 授权码两种模式的 oauth2 认证。</p>
<p>本篇源码微服务客户端对应的源码地址为: <a href="https://github.com/huzhicheng/spring-cloud-study/tree/master/oauth2/oauth2-client-authorization-code-server">点击查看 github 源码</a></p>
]]></content:encoded></item><item><title>Java 上进了，JDK21马上就要来了，感受一下它的魅力</title><link>https://www.showcodeba.com/category/java/JDK21%E9%A9%AC%E4%B8%8A%E5%B0%B1%E8%A6%81%E6%9D%A5%E4%BA%86%E6%84%9F%E5%8F%97%E4%B8%80%E4%B8%8B%E5%AE%83%E7%9A%84%E9%AD%85%E5%8A%9B/</link><pubDate>Fri, 02 Jun 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/java/JDK21%E9%A9%AC%E4%B8%8A%E5%B0%B1%E8%A6%81%E6%9D%A5%E4%BA%86%E6%84%9F%E5%8F%97%E4%B8%80%E4%B8%8B%E5%AE%83%E7%9A%84%E9%AD%85%E5%8A%9B/</guid><description>目前 Java 的最新稳定版是 JDK 20，但这是个过渡版，JDK21就是 LTS 版的了，也快要发布了，在今年9月份（也就是2023年9月）就要正式发布了。 但是，</description><content:encoded><![CDATA[<p>目前 Java 的最新稳定版是 JDK 20，但这是个过渡版，JDK21就是 LTS 版的了，也快要发布了，在今年9月份（也就是2023年9月）就要正式发布了。</p>
<p>但是，猜都不用猜，你肯定还在用 Java 8 吧！</p>
<h2 id="更丝滑的并发编程模式">更丝滑的并发编程模式</h2>
<p>如果说之前的 JDK17你还觉得没必要折腾，那 JDK21确实有必要关注一下了。因为 JDK21 引入了一种新型的并发编程模式。</p>
<p>当前 Java 中的多线程并发编程绝对是另我们都非常头疼的一部分，感觉就是学起来难啃，用起来难用。但是转头看看使用其他语言的朋友们，根本就没有这个烦恼嘛，比如 GoLang，感觉人家用起来就很丝滑呢。</p>
<p>JDK21 中就在这方面做了很大的改进，让Java并发编程变得更简单一点，更丝滑一点。确切的说，在 JDK19或JDK20中就有这些改进了。</p>
<p>那具体是什么呢？让我们来具体来看一下。下面是JDK21的 Feature。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230531172947636.png"></p>
<p>其中<code>Virtual Threads</code>、<code>Scoped Values</code>、<code>Structured Concurrency</code>就是针对多线程并发编程的几个功能。我们今天也主要来说一下他们。</p>
<h3 id="虚拟线程virtual-threads">虚拟线程（Virtual Threads）</h3>
<p>虚拟线程是基于协程的线程，它们与其他语言中的协程具有相似之处，但也存在一些不同之处。</p>
<p>虚拟线程是依附于主线程的，如果主线程销毁了，那虚拟线程也不复存在。</p>
<p>相同之处：</p>
<ol>
<li>虚拟线程和协程都是轻量级的线程，它们的创建和销毁的开销都比传统的操作系统线程要小。</li>
<li>虚拟线程和协程都可以通过暂停和恢复来实现线程之间的切换，从而避免了线程上下文切换的开销。</li>
<li>虚拟线程和协程都可以使用异步和非阻塞的方式来处理任务，提高应用程序的性能和响应速度。</li>
</ol>
<p>不同之处：</p>
<ol>
<li>虚拟线程是在 JVM 层面实现的，而协程则是在语言层面实现的。因此，虚拟线程的实现可以与任何支持 JVM 的语言一起使用，而协程的实现则需要特定的编程语言支持。</li>
<li>虚拟线程是一种基于线程的协程实现，因此它们可以使用线程相关的 API，如 <code>ThreadLocal</code>、<code>Lock</code> 和 <code>Semaphore</code>。而协程则不依赖于线程，通常需要使用特定的异步编程框架和 API。</li>
<li>虚拟线程的调度是由 JVM 管理的，而协程的调度是由编程语言或异步编程框架管理的。因此，虚拟线程可以更好地与其他线程进行协作，而协程则更适合处理异步任务。</li>
</ol>
<p>总的来说，虚拟线程是一种新的线程类型，它可以提高应用程序的性能和资源利用率，同时也可以使用传统线程相关的 API。虚拟线程与协程有很多相似之处，但也存在一些不同之处。</p>
<p>虚拟线程确实可以让多线程编程变得更简单和更高效。相比于传统的操作系统线程，虚拟线程的创建和销毁的开销更小，线程上下文切换的开销也更小，因此可以大大减少多线程编程中的资源消耗和性能瓶颈。</p>
<p>使用虚拟线程，开发者可以像编写传统的线程代码一样编写代码，而无需担心线程的数量和调度，因为 JVM 会自动管理虚拟线程的数量和调度。此外，虚拟线程还支持传统线程相关的 API，如 <code>ThreadLocal</code>、<code>Lock</code> 和 <code>Semaphore</code>，这使得开发者可以更轻松地迁移传统线程代码到虚拟线程。</p>
<p>虚拟线程的引入，使得多线程编程变得更加高效、简单和安全，使得开发者能够更加专注于业务逻辑，而不必过多地关注底层的线程管理。</p>
<h3 id="结构化并发structured-concurrency">结构化并发（Structured Concurrency）</h3>
<p>结构化并发是一种编程范式，旨在通过提供结构化和易于遵循的方法来简化并发编程。使用结构化并发，开发人员可以创建更容易理解和调试的并发代码，并且不容易出现竞争条件和其他与并发有关的错误。在结构化并发中，所有并发代码都被结构化为称为任务的定义良好的工作单元。任务以结构化方式创建、执行和完成，任务的执行总是保证在其父任务完成之前完成。</p>
<p>Structured Concurrency（结构化并发）可以让多线程编程更加简单和可靠。在传统的多线程编程中，线程的启动、执行和结束是由开发者手动管理的，因此容易出现线程泄露、死锁和异常处理不当等问题。</p>
<p>使用结构化并发，开发者可以更加自然地组织并发任务，使得任务之间的依赖关系更加清晰，代码逻辑更加简洁。结构化并发还提供了一些异常处理机制，可以更好地管理并发任务中的异常，避免因为异常而导致程序崩溃或数据不一致的情况。</p>
<p>除此之外，结构化并发还可以通过限制并发任务的数量和优先级，防止资源竞争和饥饿等问题的发生。这些特性使得开发者能够更加方便地实现高效、可靠的并发程序，而无需过多关注底层的线程管理。</p>
<h3 id="作用域值scoped-values">作用域值（Scoped Values）</h3>
<p>作用域值是JDK 20中的一项功能，允许开发人员创建作用域限定的值，这些值限定于特定的线程或任务。作用域值类似于线程本地变量，但是设计为与虚拟线程和结构化并发配合使用。它们允许开发人员以结构化的方式在任务和虚拟线程之间传递值，无需复杂的同步或锁定机制。作用域值可用于在应用程序的不同部分之间传递上下文信息，例如用户身份验证或请求特定数据。</p>
<h2 id="试验一下">试验一下</h2>
<p>进行下面的探索之前，你要下载至少 JDK19或者直接下载 JDK20，JDK 20 目前（截止到2023年9月份）是正式发布的最高版本，如果你用 JDK 19的话，没办法体验到Scoped Values的功能。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230428164027447.png"></p>
<p>或者是直接下载 JDK 21 的 Early-Access Builds（早期访问版本）。在这个地址下载 「https://jdk.java.net/21/」，下载对应的版本。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230428165054158.png"></p>
<p>如果你用的是 IDEA ，那你的IDEA 版本最起码是2022.3 这个版本或者之后的，否则不支持这么新的 JDK 版本。</p>
<p>如果你用的是 JDK19或者 JDK20的话，要在你的项目设置中将 <code>language level</code>设置为19或20的 Preview 级别，否则编译的时候会提示你无法使用预览版的功能，虚拟线程就是预览版的功能。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230428165350127.png"></p>
<p>如果你用的是 JDK21的话，将 <code>language level</code> 设置为 <code>X -Experimental Features</code>，另外，因为 JDK21不属于正式版本，所以需要到 IDEA 的设置中（注意是 IDEA 的设置，不是项目的设置了），将这个项目的 <code>Target bytecode version</code>手动修改为21，目前可选的最高就是20，也就是JDK20。设置为21之后，就可以使用 JDK21中的这些功能了。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230531214527330.png"></p>
<h2 id="虚拟线程的例子">虚拟线程的例子</h2>
<p>我们现在启动线程是怎么做的呢？</p>
<p>先声明一个线程类，<code>implements</code> 自 <code>Runnable</code>，并实现 <code>run</code>方法。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">SimpleThread</span><span class="w"> </span><span class="kd">implements</span><span class="w"> </span><span class="n">Runnable</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">run</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;当前线程名称：&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">Thread</span><span class="p">.</span><span class="na">currentThread</span><span class="p">().</span><span class="na">getName</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">Thread</span><span class="p">.</span><span class="na">sleep</span><span class="p">(</span><span class="n">1000</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="n">InterruptedException</span><span class="w"> </span><span class="n">e</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">RuntimeException</span><span class="p">(</span><span class="n">e</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>然后就可以使用这个线程类，然后启动线程了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Thread</span><span class="w"> </span><span class="n">thread</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">Thread</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">SimpleThread</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">thread</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><p>中规中矩，没毛病。</p>
<p>有了虚拟线程之后呢，怎么实现呢？</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Thread</span><span class="p">.</span><span class="na">ofPlatform</span><span class="p">().</span><span class="na">name</span><span class="p">(</span><span class="s">&#34;thread-test&#34;</span><span class="p">).</span><span class="na">start</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">SimpleThread</span><span class="p">());</span><span class="w">
</span></span></span></code></pre></div><p>下面是几种使用虚拟线程的方式。</p>
<p>1、直接启动一个虚拟线程</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Thread</span><span class="w"> </span><span class="n">thread</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Thread</span><span class="p">.</span><span class="na">startVirtualThread</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">SimpleThread</span><span class="p">());</span><span class="w">
</span></span></span></code></pre></div><p>2、使用 ofVirtual()，builder 方式启动虚拟线程，可以设置线程名称、优先级、异常处理等配置</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Thread</span><span class="p">.</span><span class="na">ofVirtual</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">name</span><span class="p">(</span><span class="s">&#34;thread-test&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">start</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">SimpleThread</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">//或者</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">Thread</span><span class="w"> </span><span class="n">thread</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Thread</span><span class="p">.</span><span class="na">ofVirtual</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">.</span><span class="na">name</span><span class="p">(</span><span class="s">&#34;thread-test&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">.</span><span class="na">uncaughtExceptionHandler</span><span class="p">((</span><span class="n">t</span><span class="p">,</span><span class="w"> </span><span class="n">e</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="na">getName</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">.</span><span class="na">unstarted</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">SimpleThread</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">thread</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><p>3、使用 Factory 创建线程</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">ThreadFactory</span><span class="w"> </span><span class="n">factory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Thread</span><span class="p">.</span><span class="na">ofVirtual</span><span class="p">().</span><span class="na">factory</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">Thread</span><span class="w"> </span><span class="n">thread</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">factory</span><span class="p">.</span><span class="na">newThread</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">SimpleThread</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">thread</span><span class="p">.</span><span class="na">setName</span><span class="p">(</span><span class="s">&#34;thread-test&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">thread</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><p>4、使用 Executors 方式</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">ExecutorService</span><span class="w"> </span><span class="n">executorService</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Executors</span><span class="p">.</span><span class="na">newVirtualThreadPerTaskExecutor</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">Future</span><span class="o">&lt;?&gt;</span><span class="w"> </span><span class="n">submit</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">executorService</span><span class="p">.</span><span class="na">submit</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">SimpleThread</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">Object</span><span class="w"> </span><span class="n">o</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">submit</span><span class="p">.</span><span class="na">get</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><h2 id="结构化编程的例子">结构化编程的例子</h2>
<p>想一下下面这个场景，假设你有三个任务要同时进行，只要任意一个任务执行完成并返回结果了，那就可以直接用这个结果了，其他的两个任务就可以停止了。比如说一个天气服务，通过三个渠道获取天气情况，只要有一个渠道返回就可以了。</p>
<p>这种场景下， 在 Java 8 下应该怎么做呢，当然也可以了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// 执行任务并返回 Future 对象列表</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">List</span><span class="o">&lt;</span><span class="n">Future</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="n">futures</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">executor</span><span class="p">.</span><span class="na">invokeAll</span><span class="p">(</span><span class="n">tasks</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// 等待任一任务完成并获取结果</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">String</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">executor</span><span class="p">.</span><span class="na">invokeAny</span><span class="p">(</span><span class="n">tasks</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><p>使用 <code>ExecutorService</code>的<code>invokeAll</code>和<code>invokeAny</code>实现，但是会有一些额外的工作，在拿到第一个结果后，要手动关闭另外的线程。</p>
<p>而 JDK21中呢，可以用结构化编程实现。</p>
<p><code>ShutdownOnSuccess</code>捕获第一个结果并关闭任务范围以中断未完成的线程并唤醒调用线程。
适用于任意子任务的结果都可以直接使用，并且无需等待其他未完成任务的结果的情况。
它定义了获取第一个结果或在所有子任务失败时抛出异常的方法</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">IOException</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">try</span><span class="w"> </span><span class="p">(</span><span class="kd">var</span><span class="w"> </span><span class="n">scope</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">StructuredTaskScope</span><span class="p">.</span><span class="na">ShutdownOnSuccess</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="p">())</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Future</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">res1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scope</span><span class="p">.</span><span class="na">fork</span><span class="p">(()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">runTask</span><span class="p">(</span><span class="n">1</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Future</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">res2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scope</span><span class="p">.</span><span class="na">fork</span><span class="p">(()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">runTask</span><span class="p">(</span><span class="n">2</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Future</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">res3</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scope</span><span class="p">.</span><span class="na">fork</span><span class="p">(()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">runTask</span><span class="p">(</span><span class="n">3</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">scope</span><span class="p">.</span><span class="na">join</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;scope:&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">scope</span><span class="p">.</span><span class="na">result</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="n">ExecutionException</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">InterruptedException</span><span class="w"> </span><span class="n">e</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">throw</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">RuntimeException</span><span class="p">(</span><span class="n">e</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">runTask</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">i</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">InterruptedException</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Thread</span><span class="p">.</span><span class="na">sleep</span><span class="p">(</span><span class="n">1000</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kt">long</span><span class="w"> </span><span class="n">l</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">Random</span><span class="p">().</span><span class="na">nextLong</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">String</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">String</span><span class="p">.</span><span class="na">valueOf</span><span class="p">(</span><span class="n">l</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;第&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">&#34;个任务：&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">s</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">return</span><span class="w"> </span><span class="n">s</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p><strong>ShutdownOnFailure</strong></p>
<p>执行多个任务，只要有一个失败（出现异常或其他主动抛出异常情况），就停止其他未执行完的任务，使用scope.throwIfFailed捕捉并抛出异常。
如果所有任务均正常，则使用 Feture.get() 或*Feture.resultNow() 获取结果</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">IOException</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">try</span><span class="w"> </span><span class="p">(</span><span class="kd">var</span><span class="w"> </span><span class="n">scope</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">StructuredTaskScope</span><span class="p">.</span><span class="na">ShutdownOnFailure</span><span class="p">())</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Future</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">res1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scope</span><span class="p">.</span><span class="na">fork</span><span class="p">(()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">runTaskWithException</span><span class="p">(</span><span class="n">1</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Future</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">res2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scope</span><span class="p">.</span><span class="na">fork</span><span class="p">(()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">runTaskWithException</span><span class="p">(</span><span class="n">2</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Future</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">res3</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scope</span><span class="p">.</span><span class="na">fork</span><span class="p">(()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">runTaskWithException</span><span class="p">(</span><span class="n">3</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">scope</span><span class="p">.</span><span class="na">join</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">scope</span><span class="p">.</span><span class="na">throwIfFailed</span><span class="p">(</span><span class="n">Exception</span><span class="p">::</span><span class="k">new</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">res1</span><span class="p">.</span><span class="na">resultNow</span><span class="p">();</span><span class="w"> </span><span class="c1">//或 res1.get()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="n">s</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Stream</span><span class="p">.</span><span class="na">of</span><span class="p">(</span><span class="n">res1</span><span class="p">,</span><span class="w"> </span><span class="n">res2</span><span class="p">,</span><span class="n">res3</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">Future</span><span class="p">::</span><span class="n">resultNow</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">.</span><span class="na">collect</span><span class="p">(</span><span class="n">Collectors</span><span class="p">.</span><span class="na">joining</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;直接结果:&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">result</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="n">Exception</span><span class="w"> </span><span class="n">e</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//throw new RuntimeException(e);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// 有一定几率发生异常</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">runTaskWithException</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">i</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">InterruptedException</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">Thread</span><span class="p">.</span><span class="na">sleep</span><span class="p">(</span><span class="n">1000</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kt">long</span><span class="w"> </span><span class="n">l</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">Random</span><span class="p">().</span><span class="na">nextLong</span><span class="p">(</span><span class="n">3</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">l</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">throw</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">InterruptedException</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">String</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">String</span><span class="p">.</span><span class="na">valueOf</span><span class="p">(</span><span class="n">l</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;第&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">&#34;个任务：&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">s</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">return</span><span class="w"> </span><span class="n">s</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h2 id="scoped-values-的例子">Scoped Values 的例子</h2>
<p>我们肯定都用过 <code>ThreadLocal</code>，它是线程本地变量，只要这个线程没销毁，可以随时获取 ThredLocal 中的变量值。Scoped Values 也可以在线程内部随时获取变量，只不过它有个作用域的概念，超出作用域就会销毁。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">ScopedValueExample</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">final</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="n">ScopedValue</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">LoginUser</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ScopedValue</span><span class="p">.</span><span class="na">newInstance</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">InterruptedException</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">ScopedValue</span><span class="p">.</span><span class="na">where</span><span class="p">(</span><span class="n">LoginUser</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;张三&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">run</span><span class="p">(()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="k">new</span><span class="w"> </span><span class="n">Service</span><span class="p">().</span><span class="na">login</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Thread</span><span class="p">.</span><span class="na">sleep</span><span class="p">(</span><span class="n">2000</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">static</span><span class="w"> </span><span class="kd">class</span> <span class="nc">Service</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">void</span><span class="w"> </span><span class="nf">login</span><span class="p">(){</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">&#34;当前登录用户是：&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">LoginUser</span><span class="p">.</span><span class="na">get</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>上面的例子模拟一个用户登录的过程，使用 <code>ScopedValue.newInstance()</code>声明了一个 <code>ScopedValue</code>，用 <code>ScopedValue.where</code>给 <code>ScopedValue</code>设置值，并且使用 run 方法执行接下来要做的事儿，这样一来，<code>ScopedValue</code>就在 run() 的内部随时可获取了，在run方法中，模拟调用了一个service 的login方法，不用传递LoginUser这个参数，就可以直接通过<code>LoginUser.get</code>方法获取当前登录用户的值了。</p>
]]></content:encoded></item><item><title>Text Box Instant Translation Chrome Extension</title><link>https://www.showcodeba.com/category/product/2-%E6%96%87%E6%9C%AC%E6%A1%86%E5%8D%B3%E6%97%B6%E7%BF%BB%E8%AF%91Chrome%E6%8F%92%E4%BB%B6-English/</link><pubDate>Fri, 02 Jun 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/product/2-%E6%96%87%E6%9C%AC%E6%A1%86%E5%8D%B3%E6%97%B6%E7%BF%BB%E8%AF%91Chrome%E6%8F%92%E4%BB%B6-English/</guid><description>After entering content into input fields on any webpage, it can be translated into a specified target language.</description><content:encoded><![CDATA[<p><a href="https://www.moonkite.cn/category/product/2-%E6%96%87%E6%9C%AC%E6%A1%86%E5%8D%B3%E6%97%B6%E7%BF%BB%E8%AF%91Chrome%E6%8F%92%E4%BB%B6/">中文说明</a>       |        <a href="https://www.moonkite.cn/category/product/2-%E6%96%87%E6%9C%AC%E6%A1%86%E5%8D%B3%E6%97%B6%E7%BF%BB%E8%AF%91Chrome%E6%8F%92%E4%BB%B6-English/">Engilish Introduction</a></p>
<p><a href="https://chrome.google.com/webstore/detail/%E6%96%87%E6%9C%AC%E6%A1%86%E7%BF%BB%E8%AF%91/bnfhhmjlkopegjkmhpmegfdpdmfpmbmn/related?hl=zh-CN">Online Install</a></p>
<p>Offline Install：
<a href="https://pan.baidu.com/s/1Ob0-mWganRnsqGvoGQVdRg?pwd=n89a">百度网盘</a>
code: n89a</p>
<p>The Text Box Translation Extension utilizes the Google Translate functionality to instantly translate the contents of any text box in the Chrome browser into the desired target language. For example, it automatically translates the content in the text box into English and replaces the current text within the text box.</p>
<p>Video Introduction：<a href="https://www.bilibili.com/video/BV17g4y1A7Fe/?vd_source=d72d86eea40f5750277144c5880966f5">中文介绍</a>，<a href="https://youtu.be/iRNeF2LBT7U">English Introduction</a></p>
<h2 id="instructions-for-use">Instructions for Use</h2>
<p>After installation, pin the &ldquo;Text Box Translation&rdquo; plugin to the toolbar.</p>
<p><img alt="image-20230627151514437" src="https://hexo.moonkite.cn/blog/image-20230627151514437.png"></p>
<p>Click on the icon of this plugin in the browser toolbar to set the target language and trigger identifier.</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230627151633954.png"></p>
<p>After entering the trigger identifier at the end of the input content (i.e., the custom trigger identifier mentioned above), the translation action will be automatically triggered, replacing the original content with the target language.</p>
<p>Entering &ldquo;你好/lang&rdquo; in the input box will automatically translate the content to &ldquo;Hello&rdquo;.</p>
<p><strong>It can be used in all input boxes on Google, Google Bard, Twitter, Weibo, Baidu, ChatGPT, and more.</strong></p>
<p>If you encounter any issues during usage or have any suggestions, feel free to add me as a friend and let me know.</p>
<p><img src="https://hexo.moonkite.cn/blog/877shots_so%20(1).png"></p>
]]></content:encoded></item><item><title>VsCode ChatGPT 插件，一个代码助手</title><link>https://www.showcodeba.com/category/product/1-VsCode-ChatGPT-%E6%8F%92%E4%BB%B6%E4%B8%80%E4%B8%AA%E4%BB%A3%E7%A0%81%E5%8A%A9%E6%89%8B/</link><pubDate>Fri, 02 Jun 2023 08:56:23 +0800</pubDate><guid>https://www.showcodeba.com/category/product/1-VsCode-ChatGPT-%E6%8F%92%E4%BB%B6%E4%B8%80%E4%B8%AA%E4%BB%A3%E7%A0%81%E5%8A%A9%E6%89%8B/</guid><description>一个 VsCode 插件版的代码助手，通过接入 ChatGPT，直接在 VsCode 中向 ChatGPT 提问</description><content:encoded><![CDATA[<p><strong>安装地址</strong>：<a href="https://marketplace.visualstudio.com/items?itemName=fengzheng.code-chatgpt-assistant"><strong>mini ChatGPT</strong></a></p>
<p>其实很早之前就想学学 VSCode 插件开发了，但是又不知道做什么，加上我这半吊子前端水平，迟迟没有动手。</p>
<p>最近 ChatGPT 火的一塌糊涂，我也一直在用，真的非常好用，有些问题之前需要 Google 搜索，现在用 ChatGPT 基本上都能直接解决，效率提升了不少。</p>
<p>但是吧，浏览器和 IDE 来回切换又很麻烦，所以我灵机一动，那就开发个 ChatGPT 插件吧，即可以学习学习 VSCode 插件开发，又可以做一个方便的效率工具，岂不是一举两得，美哉美哉。</p>
<p>于是我就动手写了下面这个插件，叫做 <code>mini ChatGPT</code>，现在已经发布到 VSCode 插件市场了。</p>
<p>有条件的同学可以安装一下，试试效果如何。</p>
<p><strong>插件功能特点</strong></p>
<ul>
<li>界面简洁，没有多余的干扰；</li>
<li>像 ChatGPT 官网输出效果一致，打字效果实时输出；</li>
<li>支持两轮上下文（太多了浪费 tokens），大致等于支持上下文；</li>
<li>代码美化+格式化，并且可以一键复制，直接粘贴到 vscode 中；</li>
</ul>
<p><img src="https://hexo.moonkite.cn/blog/image-20230509181750193.png"></p>
<h2 id="插件安装">插件安装</h2>
<p><strong>当然了，这是需要科学上网的。</strong></p>
<p>你可以直接在 VSCode 的商店直接搜索 <code>mini ChatGPT</code>安装。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230509183628014.png"></p>
<p>也可以到应用市场上安装。</p>
<p><img src="https://hexo.moonkite.cn/blog/image-20230509183750997.png"></p>
<p>插件地址：https://marketplace.visualstudio.com/items?itemName=fengzheng.code-chatgpt-assistant</p>
<h2 id="插件使用">插件使用</h2>
<ol>
<li>点击查看-&gt;命令面板，或者使用快捷键（MacOS: Shift + Command + P，Windows:Shift + Control + P</li>
<li>输入 <code>Chat</code> 并回车。
<img src="https://hexo.moonkite.cn/blog/20230509111926.png"></li>
<li>如果是首次使用，会弹出提示输入框，在此输入 ChatGPT 的 API。使用的就是原滋原味的 ChatGPT API 接口，官网上都写的很清楚，所以需要使用插件的小伙伴提供自己账号的 API key。下面有介绍如何获取ChatGPT API key。
<img src="https://hexo.moonkite.cn/blog/20230509112123.png"></li>
<li>之后就可以使用啦
<img src="https://hexo.moonkite.cn/blog/20230509112311.png"></li>
<li>如果之后想更换 ChatGPT 的 API key，在命令面板中输入 <code>ChatGPT API</code> 即可。</li>
</ol>
<p><strong>使用建议</strong></p>
<p>此插件只有一个 webview 实现，打开之后独占一个 tab，可以设置向左或向右拆分，然后在左侧使用此插件向 ChatGPT 提问，右侧是代码文件，这样就不用来回切换了，而且可以将代码片段一键复制，然后直接运行。</p>
<p><img src="https://hexo.moonkite.cn/blog/chatgpt.gif"></p>
<p><strong>获取 ChatGPT API key</strong></p>
<ol>
<li>
<p>先准备好科学上网，不要用亚洲节点。</p>
</li>
<li>
<p>之后到 <a href="https://platform.openai.com/">OpenAI 官网</a>注册账号。</p>
</li>
<li>
<p>到<a href="https://platform.openai.com/account/api-keys">个人中心</a> 创建一个 API key。</p>
</li>
</ol>
<p><img src="https://hexo.moonkite.cn/blog/20230509133206.png"></p>
<h2 id="开发历程">开发历程</h2>
<p>作为一个后端开发，我的前端水平是非常一般的，所以代码中有瑕疵的地方还请见谅。对了，代码已经放到 GitHub  上了。</p>
<p>仓库地址：https://github.com/huzhicheng/vscode-ChatGPT</p>
<p>VSCode 插件开发，官方建议是用 TypeScript 开发，所以项目完全采用了 TS 开发。由于功能比较简单，没有那么多复杂的设置，由于边学边做，太复杂的也没考虑。整个插件的核心其实就是一个 <code>webview</code>，在webview中实现界面布局和样式，难点就在于webview与插件本身来回的数据传递，但也都是常规用法。</p>
<p>整个写代码的过程其实也就几个小时，当然过程中的遇到的一些问题我都是跟 ChatGPT 提问的，包括样式都是直接描述需求，ChatGPT 给我一个大致的框架，我再微调一下就可以了。</p>
<p>所以这样看下来，其实是我和 ChatGPT 合作开发的。</p>
<p>后来发布到应用商店，写 readme 介绍和使用说明花了一些时间。</p>
<h2 id="问题和建议">问题和建议</h2>
<p>目前的版本功能比较简单，没有 ChatGPT 官方的聊天记录列表功能，如果真的有人用的话，后期考虑加上。</p>
<p>由于ChatGPT 官方并未提供上下文的 API 支持，所以只能采用其他方式模仿上下文能力，目前的做法是记录前两轮提问和回答，然后在下次提问的时候，将前两轮的提问和回答告诉 ChatGPT ，并且明确告诉 ChatGPT，例如下面这样。</p>
<p>第一轮问题：你好。</p>
<p>第一轮回答：你好，请问有什么要帮忙的吗？</p>
<p>第二论问题：请帮我写一段 Python 爬虫抓取知乎回答的 demo。</p>
<p>第二轮回答：好的，下面是一段抓取知乎回答的 Python 示例。&hellip;.省略多行代码。</p>
<p>第三轮问题：帮我改成 Java 的。</p>
<p>这时候，我会将实际的 prompt 改成下面这样的。</p>
<blockquote>
<p>下面中括号部分是前两轮的问题和回答，只用作参考。[my question is:&ldquo;你好&rdquo;,your anwser is:&ldquo;你好，请问有什么要帮忙的吗？&quot;.my question is:&ldquo;请帮我写一段 Python 爬虫抓取知乎回答的 demo。&quot;,your anwser is:&ldquo;好的，下面是一段抓取知乎回答的 Python 示例。&hellip;.省略多行代码。&rdquo;.]</p>
<p>下面引号包含的这部分是真正的问题：&ldquo;帮我改成 Java 的。&rdquo;</p>
</blockquote>
<p>采用这种方式，几乎可以模拟上下文的操作，但是偶尔会出现错乱，这块儿还有优化空间，还会继续优化。</p>
<p>如果各位小伙伴使用过程中发现什么问题，或者有什么更好的建议，都可以加我好友直接跟我说。</p>
<p>欢迎捧场，赶紧用一用吧，觉得好用的话，可以推荐给身边的小伙伴也用一下。</p>
<p><img src="https://hexo.moonkite.cn/blog/877shots_so%20(1).png"></p>
]]></content:encoded></item></channel></rss>