-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
436 lines (207 loc) · 250 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Steam的P2P联机优化</title>
<link href="/2024/Steam%E7%9A%84P2P%E8%81%94%E6%9C%BA%E4%BC%98%E5%8C%96.html"/>
<url>/2024/Steam%E7%9A%84P2P%E8%81%94%E6%9C%BA%E4%BC%98%E5%8C%96.html</url>
<content type="html"><![CDATA[<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>最近在玩群星,但是多人联机效果非常差,然后网上调研了一下发现可以用组网工具加速提升联机效果。</p><p>使用了一段时间之后发现有时候有效果有时候没有效果,所以研究了一下。</p><h1 id="P2P实现的原理"><a href="#P2P实现的原理" class="headerlink" title="P2P实现的原理"></a>P2P实现的原理</h1><h2 id="Steam-数据报中继"><a href="#Steam-数据报中继" class="headerlink" title="Steam 数据报中继"></a>Steam 数据报中继</h2><p><a href="https://partner.steamgames.com/doc/features/multiplayer/steamdatagramrelay?l=schinese">https://partner.steamgames.com/doc/features/multiplayer/steamdatagramrelay?l=schinese</a></p><p><a href="https://partner.steamgames.com/doc/api/ISteamNetworkingSockets#CreateListenSocketP2P">https://partner.steamgames.com/doc/api/ISteamNetworkingSockets#CreateListenSocketP2P</a></p><p><a href="https://partner.steamgames.com/doc/api/ISteamNetworkingSockets#ConnectP2P">https://partner.steamgames.com/doc/api/ISteamNetworkingSockets#ConnectP2P</a></p><p>Steam的SDK中提供了可以直接用于P2P的接口,游戏只需要实现接口就实现P2P连接</p><h2 id="Steam中的对等网络及共享IP-地址"><a href="#Steam中的对等网络及共享IP-地址" class="headerlink" title="Steam中的对等网络及共享IP 地址"></a>Steam中的对等网络及共享IP 地址</h2><p><a href="https://help.steampowered.com/zh/faqs/view/1433-AD20-F11D-B71E">https://help.steampowered.com/zh/faqs/view/1433-AD20-F11D-B71E</a></p><p>参考这篇文章介绍</p><blockquote><h4 id="使用中继进行对等连接"><a href="#使用中继进行对等连接" class="headerlink" title="使用中继进行对等连接"></a>使用中继进行对等连接</h4><p>在建立直接连接时,视防火墙配置而定,NAT 遍历不一定总能成功。 如果失败,玩家就需要利用中继来和彼此交流。 每个玩家的防火墙都将接收来自中继的数据包。 (<a href="https://steamcommunity.com/linkfilter/?u=https://tailscale.com/blog/how-nat-traversal-works/">这篇文章</a>很好地详细描述了这一流程。) 使用中继不需要和另一玩家分享您的 IP 地址,这是其好处之一。 通常,只有在无法建立直接连接的情况下,才会使用中继作为最后的方案,因为这项服务开销相对较大。 在这种情况下,即使不可能建立直接连接,双方也已经相互交换了 IP 地址,因此还是有可能发生 DoS 攻击。 如果一方或双方对等端拒绝分享 IP 地址,那么就不可能建立直接连接,NAT 遍历将会请求使用中继。 双方对等端必须都分享其 IP 地址,才能建立直接连接。</p><h4 id="使用-Steam-网络-API-的游戏又如何呢?"><a href="#使用-Steam-网络-API-的游戏又如何呢?" class="headerlink" title="使用 Steam 网络 API 的游戏又如何呢?"></a>使用 Steam 网络 API 的游戏又如何呢?</h4><p>如果游戏使用了最新的网络 API(ISteamNetworkingSockets 和 ISteamNetworkingMessages),那么流量将会通过 Steam 数据报中继(SDR)进行中继。这是 Valve 遍布全球的主干和中继网络,您可以决定何时允许某款应用分享您的 IP 地址。 在 “Steam”/“设置”/“游戏中”/“Steam 网络”中,有可以控制何时分享您 IP 地址的选项:</p><ul><li>从不<br> 永不和任何玩家分享您的 IP。 永远中继对等流量。</li><li>始终<br>始终允许应用和所有玩家分享您的 IP。 如果另一玩家也分享了其地址,则可能会建立直接连>接; 否则,进行中继。</li><li>默认<br>除非出现必须避免 Ping 时间过长的情况,否则此选项不会分享您的 IP 地址。</li><li>仅限好友<br>仅与您好友列表中的用户分享您的 IP。</li></ul></blockquote><p>也就是说如果相互之间允许IP分享,Steam会自动检测NAT,如果NAT能够打通那么就会直接建立P2P连接,否则会使用服务器进行中继。</p><h1 id="检查Steam是否真的使用了P2P"><a href="#检查Steam是否真的使用了P2P" class="headerlink" title="检查Steam是否真的使用了P2P"></a>检查Steam是否真的使用了P2P</h1><p>在这一步,我们使用了N2N-V3进行了组网,使用NetLimiter检查进程所连接的IP和端口,P社的Stellaris进行验证</p><ol><li><p>开启共享IP</p><p>Steam -> 设置 -> 游戏中 -> Steam网络 -> 分享IP改为始终</p><p>不太确定是不是实时生效,所以这一步改完了之后再把Steam退掉</p></li><li><p>组网后进入游戏,加入房间</p></li></ol><p>这一步需要注意,一定要先组网并验证相互之间已经连接成功,然后再开Steam然后进游戏</p><ol start="3"><li><p>使用NetLimiter检查进程所连接的IP和端口</p><p>10.144.6.xxx是N2N组网后的虚拟内网网段</p><img src="/2024/Steam%E7%9A%84P2P%E8%81%94%E6%9C%BA%E4%BC%98%E5%8C%96/1.png" class=""><img src="/2024/Steam%E7%9A%84P2P%E8%81%94%E6%9C%BA%E4%BC%98%E5%8C%96/image-20241122122756481.png" class=""></li></ol><p>可以看到确实是走了内网,同时在同步存档的时候可以观察到Tap网卡上的大流量,不过是Steam建立了链接,推测是群星使用了Steam的SDK后由Steam转发了流量</p><h1 id="优化思路"><a href="#优化思路" class="headerlink" title="优化思路"></a>优化思路</h1><p>已经确定Steam的P2P可以走虚拟局域网,接下来就是优化N2N的效果</p><ol><li><p>目前是打算阿里云的按量计费机器,2H2G,100Mbps带宽的流量,同时支持停机不计费和定时释放实例。</p><p>会赠送20G的免费流量,按照6H小时开一把群星来算的话,<code>6H*0.11/H</code>+免费流量,费用很低,而且在用完之后可以停掉实例,后续使用的时候再开。</p></li></ol><img src="/2024/Steam%E7%9A%84P2P%E8%81%94%E6%9C%BA%E4%BC%98%E5%8C%96/2.png" class=""><img src="/2024/Steam%E7%9A%84P2P%E8%81%94%E6%9C%BA%E4%BC%98%E5%8C%96/3.png" class=""><ol start="2"><li>有时候就算N2N打通隧道,但是由于跨省结算的原因,可能会导致UDP不稳,此时可以考虑使用加上<code>-S1</code>强制走supernode中继来提升稳定性</li></ol><h1 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h1><p>感谢朋友们的帮助和配合,来和我一起来优化联机的效果。</p>]]></content>
<tags>
<tag> Steam,P2P,LAN,N2N </tag>
</tags>
</entry>
<entry>
<title>实现一个能够自动删除的临时文件</title>
<link href="/2024/%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%83%BD%E5%A4%9F%E8%87%AA%E5%8A%A8%E5%88%A0%E9%99%A4%E7%9A%84%E4%B8%B4%E6%97%B6%E6%96%87%E4%BB%B6.html"/>
<url>/2024/%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%83%BD%E5%A4%9F%E8%87%AA%E5%8A%A8%E5%88%A0%E9%99%A4%E7%9A%84%E4%B8%B4%E6%97%B6%E6%96%87%E4%BB%B6.html</url>
<content type="html"><![CDATA[<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>前段时间在弄关于文档转图片然后进行处理的工作,其中由于图片内容会多次读取,所以将其持久化在本地。<br>但是由于图片是临时文件,所以需要在使用完之后删除。<br>最初的实现是使用try-with-resource,但是随着流程变得复杂,图片的生命周期变得不可控,所以需要一个更好的解决方案。</p><h1 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h1><ol><li>利用Cleaner的特性,在文件被GC回收时,进行删除操作。需要注意不要在cleaner中应用<code>this</code>对象,否则会导致内存泄漏。</li><li>利用MDC保存当前线程的MDC信息,这样cleaner执行时,用于跟踪上下文的MDC可以保留,使得traceId等信息不会丢失。</li></ol><h1 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h1><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.haizhi.metis.document.parse.common;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.File;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.lang.ref.Cleaner;</span><br><span class="line"><span class="keyword">import</span> java.nio.file.Path;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"><span class="keyword">import</span> java.util.UUID;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> lombok.Getter;</span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"><span class="keyword">import</span> org.jetbrains.annotations.NotNull;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.MDC;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@Getter</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AutoCleanCacheFile</span> <span class="keyword">extends</span> <span class="title class_">File</span> {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Cleaner</span> <span class="variable">cleaner</span> <span class="operator">=</span> Cleaner.create();</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> <span class="type">String</span> <span class="variable">TMP_FILE_PREFIX</span> <span class="operator">=</span> <span class="string">"temp_"</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">tmpdir</span> <span class="operator">=</span> System.getProperty(<span class="string">"java.io.tmpdir"</span>);</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">serialVersionUID</span> <span class="operator">=</span> <span class="number">1L</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String fileType;</span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">AutoCleanCacheFile</span><span class="params">(<span class="meta">@NotNull</span> String fileType)</span> {</span><br><span class="line"> <span class="built_in">super</span>(createTempFile(fileType));</span><br><span class="line"> <span class="built_in">this</span>.fileType = fileType;</span><br><span class="line"> <span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> toPath();</span><br><span class="line"> Map<String, String> mdcMap= MDC.getCopyOfContextMap();</span><br><span class="line"> cleaner.register(<span class="built_in">this</span>, () -> {</span><br><span class="line"> <span class="comment">//备份MDC用于还原</span></span><br><span class="line"> Map<String, String> backup = MDC.getCopyOfContextMap();</span><br><span class="line"> MDC.setContextMap(mdcMap);</span><br><span class="line"> <span class="type">boolean</span> <span class="variable">delete</span> <span class="operator">=</span> path.toFile().delete();</span><br><span class="line"> log.debug(<span class="string">"delete temp File [{}]: {}"</span>, delete, path);</span><br><span class="line"> <span class="keyword">if</span> (backup != <span class="literal">null</span>){</span><br><span class="line"> MDC.setContextMap(backup);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> String <span class="title function_">createTempFile</span><span class="params">(<span class="meta">@NotNull</span> String fileType)</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="type">String</span> <span class="variable">path</span> <span class="operator">=</span> Path.of(tmpdir, String.format(<span class="string">"%s%s.%s"</span>, TMP_FILE_PREFIX, UUID.randomUUID(), fileType)).toString();</span><br><span class="line"> <span class="type">boolean</span> <span class="variable">newFile</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(path).createNewFile();</span><br><span class="line"> log.debug(<span class="string">"create temp File [{}]: {}"</span>, newFile, path);</span><br><span class="line"> <span class="keyword">return</span> path;</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> <span class="comment">//出现未知异常,直接抛出</span></span><br><span class="line"> <span class="comment">//理论上不应该进入这里</span></span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> AutoCleanCacheFile <span class="title function_">build</span><span class="params">(<span class="meta">@NotNull</span> String fileType)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AutoCleanCacheFile</span>(fileType);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag> GC </tag>
<tag> JAVA </tag>
</tags>
</entry>
<entry>
<title>让自定义注解支持配置属性注入</title>
<link href="/2024/%E8%AE%A9%E8%87%AA%E5%AE%9A%E4%B9%89%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E6%B3%A8%E5%85%A5.html"/>
<url>/2024/%E8%AE%A9%E8%87%AA%E5%AE%9A%E4%B9%89%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E6%B3%A8%E5%85%A5.html</url>
<content type="html"><![CDATA[<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>在写代码的过程中需要用到一些自定义annotation,但是annotation中的String类型的value需要字符串常量,于是想是否可以将<code>@Value</code>所支持的表达式移植到自定义注解中。</p><h1 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h1><ol><li>Spring中的具体实现</li></ol><blockquote><p>org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Object</span> <span class="variable">value</span> <span class="operator">=</span> getAutowireCandidateResolver().getSuggestedValue(descriptor);</span><br><span class="line"><span class="keyword">if</span> (value != <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (value <span class="keyword">instanceof</span> String) {</span><br><span class="line"> <span class="type">String</span> <span class="variable">strVal</span> <span class="operator">=</span> resolveEmbeddedValue((String) value);</span><br><span class="line"> <span class="type">BeanDefinition</span> <span class="variable">bd</span> <span class="operator">=</span> (beanName != <span class="literal">null</span> && containsBean(beanName) ?</span><br><span class="line"> getMergedBeanDefinition(beanName) : <span class="literal">null</span>);</span><br><span class="line"> value = evaluateBeanDefinitionString(strVal, bd);</span><br><span class="line"> }</span><br><span class="line"> <span class="type">TypeConverter</span> <span class="variable">converter</span> <span class="operator">=</span> (typeConverter != <span class="literal">null</span> ? typeConverter : getTypeConverter());</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">return</span> converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());</span><br><span class="line"> } <span class="keyword">catch</span> (UnsupportedOperationException ex) {</span><br><span class="line"> <span class="comment">// A custom TypeConverter which does not support TypeDescriptor resolution...</span></span><br><span class="line"> <span class="keyword">return</span> (descriptor.getField() != <span class="literal">null</span> ?</span><br><span class="line"> converter.convertIfNecessary(value, type, descriptor.getField()) :</span><br><span class="line"> converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>以上是核心代码</p><ul><li><code>resolveEmbeddedValue</code>方法用于执行<code>${}</code>类型的表达式,如<code>${spring.application.name}</code>,可以实现将配置文件中的值注入到表达式中。</li><li><code>evaluateBeanDefinitionString</code>方法用于执行<code>#${}</code>类型表达式,如<code>#{T(System).currentTimeMillis()}</code>、<code>#{systemProperties['user.name']}</code>,有一定的代码执行能力。</li></ul><ol start="2"><li>抽取所需要的代码</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <T> T <span class="title function_">contextLoads</span><span class="params">(ConfigurableBeanFactory beanFactory, String expression, Class<T> resultType)</span> {</span><br><span class="line"> <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> beanFactory.resolveEmbeddedValue(expression);</span><br><span class="line"> <span class="type">T</span> <span class="variable">result</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (Objects.nonNull(beanFactory.getBeanExpressionResolver())) {</span><br><span class="line"> <span class="type">Object</span> <span class="variable">evaluateResult</span> <span class="operator">=</span> beanFactory.getBeanExpressionResolver().evaluate(s,</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">BeanExpressionContext</span>(beanFactory, <span class="literal">null</span>));</span><br><span class="line"> <span class="type">TypeConverter</span> <span class="variable">converter</span> <span class="operator">=</span> beanFactory.getTypeConverter();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> result = converter.convertIfNecessary(evaluateResult, resultType);</span><br><span class="line"> } <span class="keyword">catch</span> (UnsupportedOperationException ex) {</span><br><span class="line"> <span class="comment">// A custom TypeConverter which does not support TypeDescriptor resolution...</span></span><br><span class="line"> log.error(<span class="string">"UnsupportedOperationException"</span>, ex);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="使用样例"><a href="#使用样例" class="headerlink" title="使用样例"></a>使用样例</h1><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ApplicationTests</span> {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(ApplicationTests.class);</span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> ConfigurableBeanFactory beanFactory;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="keyword">void</span> <span class="title function_">name</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">Long</span> <span class="variable">currentTimeMillis</span> <span class="operator">=</span> contextLoads(beanFactory, <span class="string">"#{T(System).currentTimeMillis()}"</span>, Long.class);</span><br><span class="line"> System.out.println(currentTimeMillis);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <T> T <span class="title function_">contextLoads</span><span class="params">(ConfigurableBeanFactory beanFactory, String expression, Class<T> resultType)</span> {</span><br><span class="line"> <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> beanFactory.resolveEmbeddedValue(expression);</span><br><span class="line"> <span class="type">T</span> <span class="variable">result</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (Objects.nonNull(beanFactory.getBeanExpressionResolver())) {</span><br><span class="line"> <span class="type">Object</span> <span class="variable">evaluateResult</span> <span class="operator">=</span> beanFactory.getBeanExpressionResolver().evaluate(s,</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">BeanExpressionContext</span>(beanFactory, <span class="literal">null</span>));</span><br><span class="line"> <span class="type">TypeConverter</span> <span class="variable">converter</span> <span class="operator">=</span> beanFactory.getTypeConverter();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> result = converter.convertIfNecessary(evaluateResult, resultType);</span><br><span class="line"> } <span class="keyword">catch</span> (UnsupportedOperationException ex) {</span><br><span class="line"> <span class="comment">// A custom TypeConverter which does not support TypeDescriptor resolution...</span></span><br><span class="line"> log.error(<span class="string">"UnsupportedOperationException"</span>, ex);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag> Java </tag>
<tag> Spring </tag>
</tags>
</entry>
<entry>
<title>SpringCloudFeign中的超时时间配置</title>
<link href="/2024/SpringCloudFeign%E4%B8%AD%E7%9A%84%E8%B6%85%E6%97%B6%E6%97%B6%E9%97%B4%E9%85%8D%E7%BD%AE.html"/>
<url>/2024/SpringCloudFeign%E4%B8%AD%E7%9A%84%E8%B6%85%E6%97%B6%E6%97%B6%E9%97%B4%E9%85%8D%E7%BD%AE.html</url>
<content type="html"><![CDATA[<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>最近在排查一个关于超时时间的bug,跟踪代码发现了一个以前没注意到的点</p><p><a href="https://github.com/spring-cloud/spring-cloud-openfeign/issues/324">https://github.com/spring-cloud/spring-cloud-openfeign/issues/324</a></p><h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># org.springframework.cloud.openfeign.FeignClientFactoryBean#configureUsingProperties</span><br><span class="line"><span class="title function_">if</span> <span class="params">(config.getConnectTimeout()</span> != <span class="literal">null</span> && config.getReadTimeout() != <span class="literal">null</span>) {</span><br><span class="line"> builder.options(<span class="keyword">new</span> <span class="title class_">Request</span>.Options(config.getConnectTimeout(),</span><br><span class="line"> config.getReadTimeout()));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在使用配置文件设置feign-client的时候,只有readTimeout和connectTimeout配置同时设置才会起效,具体的判断逻辑在<code>org.springframework.cloud.openfeign.FeignClientFactoryBean#configureUsingProperties</code>中。</p><h1 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h1><h2 id="升级依赖-推荐"><a href="#升级依赖-推荐" class="headerlink" title="升级依赖(推荐)"></a>升级依赖(推荐)</h2><blockquote><p><a href="https://github.com/spring-cloud/spring-cloud-openfeign/issues/324">https://github.com/spring-cloud/spring-cloud-openfeign/issues/324</a></p></blockquote><p>可以看到在新版本中已经修复了这个问题</p><h2 id="同时设置readTimeout和connectTimeout"><a href="#同时设置readTimeout和connectTimeout" class="headerlink" title="同时设置readTimeout和connectTimeout"></a>同时设置readTimeout和connectTimeout</h2><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">feign:</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="attr">config:</span></span><br><span class="line"> <span class="attr">xxx:</span></span><br><span class="line"> <span class="attr">readTimeout:</span> <span class="string">xx</span></span><br><span class="line"> <span class="attr">connectTimeout:</span> <span class="string">xx</span></span><br></pre></td></tr></table></figure><h2 id="使用配置类"><a href="#使用配置类" class="headerlink" title="使用配置类"></a>使用配置类</h2><p>使用配置类设置readTimeout和connectTimeout的时候两个参数必须同时传递</p><img src="/2024/SpringCloudFeign%E4%B8%AD%E7%9A%84%E8%B6%85%E6%97%B6%E6%97%B6%E9%97%B4%E9%85%8D%E7%BD%AE/20240124103348621.png" class="" title="image-20240124103348621">]]></content>
<tags>
<tag> feign </tag>
</tags>
</entry>
<entry>
<title>windows上游戏加速器与WSL共存</title>
<link href="/2024/windows%E4%B8%8A%E6%B8%B8%E6%88%8F%E5%8A%A0%E9%80%9F%E5%99%A8%E4%B8%8EWSL%E5%85%B1%E5%AD%98.html"/>
<url>/2024/windows%E4%B8%8A%E6%B8%B8%E6%88%8F%E5%8A%A0%E9%80%9F%E5%99%A8%E4%B8%8EWSL%E5%85%B1%E5%AD%98.html</url>
<content type="html"><![CDATA[<h1 id="现象"><a href="#现象" class="headerlink" title="现象"></a>现象</h1><p>发现在使用加速器之后,WSL相关的应用会出现无法启动的情况,需要重置网络。</p><p>参考Github上的解决方案<a href="https://github.com/microsoft/WSL/issues/4177#issuecomment-597736482%EF%BC%8C%E6%8C%89%E7%85%A7%E4%BB%A5%E4%B8%8B%E6%AD%A5%E9%AA%A4%E5%8D%B3%E5%8F%AF%E3%80%82">https://github.com/microsoft/WSL/issues/4177#issuecomment-597736482,按照以下步骤即可。</a></p><ol><li>下载NOLSP.exe</li></ol><p><a href="http://www.proxifier.com/tmp/Test20200228/NoLsp.exe">www.proxifier.com/tmp/Test20200228/NoLsp.exe</a></p><ol start="2"><li>以管理员权限执行以下命令</li></ol><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">NoLsp.exe <span class="string">"C:\Program Files\WSL\wsl.exe"</span></span><br><span class="line">NoLsp.exe <span class="string">"C:\Program Files\WSL\wslservice.exe"</span></span><br><span class="line">NoLsp.exe <span class="string">"C:\Program Files\WSL\wslhost.exe"</span></span><br><span class="line">NoLsp.exe <span class="string">"C:\Program Files\WSL\wslrelay.exe"</span></span><br><span class="line">taskkill <span class="literal">-IM</span> <span class="string">"wslservice.exe"</span> /F</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag> wsl </tag>
<tag> https </tag>
</tags>
</entry>
<entry>
<title>conda环境中共享库找不到问题原因及解决方案</title>
<link href="/2023/conda%E7%8E%AF%E5%A2%83%E4%B8%AD%E5%85%B1%E4%BA%AB%E5%BA%93%E6%89%BE%E4%B8%8D%E5%88%B0%E9%97%AE%E9%A2%98%E5%8E%9F%E5%9B%A0%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.html"/>
<url>/2023/conda%E7%8E%AF%E5%A2%83%E4%B8%AD%E5%85%B1%E4%BA%AB%E5%BA%93%E6%89%BE%E4%B8%8D%E5%88%B0%E9%97%AE%E9%A2%98%E5%8E%9F%E5%9B%A0%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.html</url>
<content type="html"><![CDATA[<h3 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h3><p>今天使用micromamba新建了一个环境来试试latex-ocr+paddle ocr的效果</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">micromamba create -n latex-ocr python=3.8 cudatoolkit=10.2 cudnn</span><br></pre></td></tr></table></figure><p>环境创建完成之后切换环境并安装依赖</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">micromamba activate latex-ocr</span><br><span class="line">pip install -r requirements.txt</span><br></pre></td></tr></table></figure><p>然后开始跑代码</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python main.py</span><br></pre></td></tr></table></figure><h3 id="发现问题"><a href="#发现问题" class="headerlink" title="发现问题"></a>发现问题</h3><p>在跑代码的时候发现报了一个库没有找到的问题</p><img src="/2023/conda%E7%8E%AF%E5%A2%83%E4%B8%AD%E5%85%B1%E4%BA%AB%E5%BA%93%E6%89%BE%E4%B8%8D%E5%88%B0%E9%97%AE%E9%A2%98%E5%8E%9F%E5%9B%A0%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/2_1.png" class="" title="libcudart.so.10.2"><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">Error: Can not import paddle core while this file exists: /root/micromamba/envs/latex-ocr/lib/python3.8/site-packages/paddle/fluid/libpaddle.so</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line"> File "main.py", line 6, in <module></span><br><span class="line"> import apis.latex_ocr</span><br><span class="line"> File "/root/latex-ocr/apis/latex_ocr.py", line 6, in <module></span><br><span class="line"> from paddle_detection.engine import EngineWrapper</span><br><span class="line"> File "/root/latex-ocr/paddle_detection/engine.py", line 1, in <module></span><br><span class="line"> from paddleocr import PPStructure</span><br><span class="line"> File "/root/micromamba/envs/latex-ocr/lib/python3.8/site-packages/paddleocr/__init__.py", line 14, in <module></span><br><span class="line"> from .paddleocr import *</span><br><span class="line"> File "/root/micromamba/envs/latex-ocr/lib/python3.8/site-packages/paddleocr/paddleocr.py", line 21, in <module></span><br><span class="line"> import paddle</span><br><span class="line"> File "/root/micromamba/envs/latex-ocr/lib/python3.8/site-packages/paddle/__init__.py", line 25, in <module></span><br><span class="line"> from .framework import monkey_patch_variable</span><br><span class="line"> File "/root/micromamba/envs/latex-ocr/lib/python3.8/site-packages/paddle/framework/__init__.py", line 17, in <module></span><br><span class="line"> from . import random # noqa: F401</span><br><span class="line"> File "/root/micromamba/envs/latex-ocr/lib/python3.8/site-packages/paddle/framework/random.py", line 16, in <module></span><br><span class="line"> import paddle.fluid as fluid</span><br><span class="line"> File "/root/micromamba/envs/latex-ocr/lib/python3.8/site-packages/paddle/fluid/__init__.py", line 36, in <module></span><br><span class="line"> from . import framework</span><br><span class="line"> File "/root/micromamba/envs/latex-ocr/lib/python3.8/site-packages/paddle/fluid/framework.py", line 37, in <module></span><br><span class="line"> from . import core</span><br><span class="line"> File "/root/micromamba/envs/latex-ocr/lib/python3.8/site-packages/paddle/fluid/core.py", line 338, in <module></span><br><span class="line"> raise e</span><br><span class="line"> File "/root/micromamba/envs/latex-ocr/lib/python3.8/site-packages/paddle/fluid/core.py", line 274, in <module></span><br><span class="line"> from . import libpaddle</span><br><span class="line">ImportError: libcudart.so.10.2: cannot open shared object file: No such file or directory</span><br></pre></td></tr></table></figure><p>但是这个库是存在lib目录下的</p><h3 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h3><h4 id="conda共享库隔离的实现原理"><a href="#conda共享库隔离的实现原理" class="headerlink" title="conda共享库隔离的实现原理"></a>conda共享库隔离的实现原理</h4><p>Windows上conda隔离共享库的方案与Linux不同</p><h5 id="Linux"><a href="#Linux" class="headerlink" title="Linux"></a>Linux</h5><p>linux平台上,conda中下载下来的python二进制文件是被修改过的,通过<code>readelf</code>命令可以看到以下内容</p><img src="/2023/conda%E7%8E%AF%E5%A2%83%E4%B8%AD%E5%85%B1%E4%BA%AB%E5%BA%93%E6%89%BE%E4%B8%8D%E5%88%B0%E9%97%AE%E9%A2%98%E5%8E%9F%E5%9B%A0%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/3.1.1_1.png" class=""><p><code>Library rpath: [$ORIGIN/../lib]</code>这部分是conda对python二进制文件进行的修改,<code>$ORIGIN</code>代表的意思是文件所在的目录,<code>$ORIGIN/../lib</code>所代表的意思是与文件所在目录同级的<code>lib</code>目录。</p><blockquote><p>在Linux中,动态库的搜索路径优先级为:</p><ol><li>rpath</li><li>LD_LIBRARY_PATH环境变量指定的目录</li><li>runpath</li><li>搜索/etc/ld.so.cache缓存</li><li>默认的系统库目录,/lib或者/usr/lib等</li></ol></blockquote><p>conda中隔离环境中的共享库所安装的目录在<code>env/xxx/lib</code>,python命令所在的目录为<code>env/xxx/bin</code>,</p><p>所以当使用conda中下载的python解释器时,<code>$ORIGIN/../lib</code>的优先级最高,会优先使用conda安装到隔离环境,也就是<code>env/xxx/lib</code>中的共享库。</p><h5 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h5><p>windows中就相对糙快猛了,conda将python命令放在<code>env/xxx/</code>目录下,共享库放在<code>env/xxx/Library/bin</code>下,两个目录都被加入到<code>PATH</code>环境变量中,每次执行<code>conda activate</code>命令时会切换<code>PATH</code>中两个目录的地址,实现切换共享库环境。</p><img src="/2023/conda%E7%8E%AF%E5%A2%83%E4%B8%AD%E5%85%B1%E4%BA%AB%E5%BA%93%E6%89%BE%E4%B8%8D%E5%88%B0%E9%97%AE%E9%A2%98%E5%8E%9F%E5%9B%A0%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/3.1.2_1.png" class="" title="3.1.2_1"><h4 id="问题的原因"><a href="#问题的原因" class="headerlink" title="问题的原因"></a>问题的原因</h4><p>当时运行的环境是Linux,上一节中的方案应该是没有问题的才对,为什么还会找不到库呢?</p><p>加上<code>LD_DEBUG=libs</code>环境变量再执行一次代码,看看程序是如何寻找缺少的<code>libcudart.so.10.2</code>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">LD_DEBUG=libs python main.py</span><br></pre></td></tr></table></figure><p>从输出中搜索关键字,可以定位到以下内容:</p><img src="/2023/conda%E7%8E%AF%E5%A2%83%E4%B8%AD%E5%85%B1%E4%BA%AB%E5%BA%93%E6%89%BE%E4%B8%8D%E5%88%B0%E9%97%AE%E9%A2%98%E5%8E%9F%E5%9B%A0%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/3.2.1_1.png" class=""><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">87054:find library=libcudart.so.10.2 [0]; searching</span><br><span class="line">87054: search path=/root/micromamba/envs/latex-ocr/lib/python3.8/site-packages/paddle/fluid/../libs</span><br><span class="line"> (RUNPATH from file /root/micromamba/envs/latex-ocr/lib/python3.8/site-packages/paddle/fluid/libpaddle.so)</span><br><span class="line">87054: trying file=/root/micromamba/envs/latex-ocr/lib/python3.8/site-packages/paddle/fluid/../libs/libcudart.so.10.2</span><br><span class="line">87054: search cache=/etc/ld.so.cache</span><br><span class="line">87054: search path=/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu:/lib:/usr/lib(system search path)</span><br><span class="line">87054: trying file=/lib/x86_64-linux-gnu/libcudart.so.10.2</span><br><span class="line">87054: trying file=/usr/lib/x86_64-linux-gnu/libcudart.so.10.2</span><br><span class="line">87054: trying file=/lib/libcudart.so.10.2</span><br><span class="line">87054: trying file=/usr/lib/libcudart.so.10.2</span><br></pre></td></tr></table></figure><p>可以看到是<code>libpaddle.so</code>尝试调用<code>libcudart.so.10.2</code>,按顺序尝试了以下目录:</p><ul><li><code>RUNPATH</code>所指定的目录<code>/root/micromamba/envs/latex-ocr/lib/python3.8/site-packages/paddle/fluid/../libs</code></li><li>搜索<code>/etc/ld.so.cache</code>缓存</li><li><code>/lib/x86_64-linux-gnu</code></li><li><code>/usr/lib/x86_64-linux-gnu</code></li><li><code>/lib</code></li><li><code>/usr/lib</code></li></ul><p>虽然conda安装的python解释器通过<code>rpath</code>的方式进行了修改,但是对于paddle的库中so是不生效的,conda把库安装在<code>/root/micromamba/envs/latex-ocr/lib</code>,所以找不到。</p><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><p>解决方法很简单,指定一个变量来运行就好了</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">LD_LIBRARY_PATH=/root/micromamba/envs/latex-ocr/lib python main.py</span><br></pre></td></tr></table></figure><blockquote><p><a href="https://zhuanlan.zhihu.com/p/534778561">https://zhuanlan.zhihu.com/p/534778561</a></p><p><a href="https://amir.rachum.com/shared-libraries/#runtime-search-path">https://amir.rachum.com/shared-libraries/#runtime-search-path</a></p></blockquote>]]></content>
<tags>
<tag> python </tag>
<tag> conda </tag>
<tag> mamba </tag>
<tag> microconda </tag>
<tag> micromamba </tag>
</tags>
</entry>
<entry>
<title>基于语法树对文章中的章节数据进行匹配</title>
<link href="/2023/%E5%9F%BA%E4%BA%8E%E8%AF%AD%E6%B3%95%E6%A0%91%E5%AF%B9%E6%96%87%E7%AB%A0%E4%B8%AD%E7%9A%84%E7%AB%A0%E8%8A%82%E6%95%B0%E6%8D%AE%E8%BF%9B%E8%A1%8C%E5%8C%B9%E9%85%8D.html"/>
<url>/2023/%E5%9F%BA%E4%BA%8E%E8%AF%AD%E6%B3%95%E6%A0%91%E5%AF%B9%E6%96%87%E7%AB%A0%E4%B8%AD%E7%9A%84%E7%AB%A0%E8%8A%82%E6%95%B0%E6%8D%AE%E8%BF%9B%E8%A1%8C%E5%8C%B9%E9%85%8D.html</url>
<content type="html"><![CDATA[<h3 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h3><p>之前有尝试过使用正则提取一篇文章中的标题信息,并还原其中的层级,碰到以下几个问题:</p><ul><li>正则规则复杂,调试不方便</li><li>正则对于匹配到标题中的编号信息需要在代码中进行二次处理,处理的步骤也很麻烦,需要考虑多种边界条件</li><li>当正则变动时,对应的代码也需要进行变动</li></ul><p>突发奇想是否可以使用ANTLR4通过构建语法树的方式来解决这几个问题</p><h3 id="构建语法树"><a href="#构建语法树" class="headerlink" title="构建语法树"></a>构建语法树</h3><h4 id="标题识别的思路"><a href="#标题识别的思路" class="headerlink" title="标题识别的思路"></a>标题识别的思路</h4><p>常见标题样式可分为以下两种:</p><ul><li><p><code>(左侧分隔符)</code> <code>编号</code> <code> 右侧分隔符</code> <code>正文</code>,比如:</p><ul><li>第一章</li><li>第一节</li><li>1:</li><li>2:</li></ul><p>左侧分隔符不一定存在,但是右侧分隔符会存在</p></li><li><p><code>编号</code>.<code>编号</code>.<code>编号</code> <code>右侧分隔符</code> <code>正文</code>,比如:</p><ul><li>1</li><li>1.1</li><li>1.1.1</li></ul></li></ul><p>对于列举式,像带有<code>如下列所述</code>等字眼,通过a 、b、c等序号一条一条列举出来的暂时不在文本考虑范围内。</p><h4 id="开始构建语法树"><a href="#开始构建语法树" class="headerlink" title="开始构建语法树"></a>开始构建语法树</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">grammar TitleDetect;</span><br><span class="line"></span><br><span class="line">//代表整个段落,有3个部分</span><br><span class="line">expr:</span><br><span class="line">title_begin ? num_seg splite</span><br><span class="line">;</span><br><span class="line">//代表段落第一部分,标题的开始</span><br><span class="line">title_begin:</span><br><span class="line">TITLE_BEGIN</span><br><span class="line">;</span><br><span class="line">//代表段落第二部分,编号部分</span><br><span class="line">num_seg:</span><br><span class="line">(num_cn|num) (multive_level_num_splite (num_cn|num))*</span><br><span class="line">;</span><br><span class="line">//代表段落第三部分,分隔符</span><br><span class="line">splite:</span><br><span class="line">(SPLITE_CHAR|SPLITE_SYMBLE|WS|MULTIVE_LEVEL_NUM_SPLITE|NOT_NATURE_CHAR)+</span><br><span class="line">;</span><br><span class="line"></span><br><span class="line">num_cn:</span><br><span class="line">NUM_CN</span><br><span class="line">;</span><br><span class="line">num:</span><br><span class="line">NUM</span><br><span class="line">;</span><br><span class="line"></span><br><span class="line">multive_level_num_splite:</span><br><span class="line">MULTIVE_LEVEL_NUM_SPLITE</span><br><span class="line">;</span><br><span class="line"></span><br><span class="line">WS : [ \t\r] ; // spaces, tabs</span><br><span class="line">TITLE_BEGIN : '第'; // 匹配开头</span><br><span class="line">NUM : [0-9]+; // 匹配阿拉伯数字</span><br><span class="line">NUM_CN : [一二三四五六七八九十]+; // 匹配中文阿拉伯数字</span><br><span class="line">MULTIVE_LEVEL_NUM_SPLITE : [.]+;</span><br><span class="line">SPLITE_SYMBLE : [。.,、,::]+;</span><br><span class="line">SPLITE_CHAR : [条章节话目]+;</span><br><span class="line">NOT_NATURE_CHAR: ~[a-zA-Z0-9\u4e00-\u9fa5];</span><br><span class="line">CHAE: .;</span><br></pre></td></tr></table></figure><h4 id="使用antlr4-maven-plugin插件进行编译"><a href="#使用antlr4-maven-plugin插件进行编译" class="headerlink" title="使用antlr4-maven-plugin插件进行编译"></a>使用antlr4-maven-plugin插件进行编译</h4><p>插件文档地址: <a href="https://www.antlr.org/api/maven-plugin/latest/antlr4-mojo.html">https://www.antlr.org/api/maven-plugin/latest/antlr4-mojo.html</a></p><p>在pom.xml中加入antlr4-maven-plugin插件</p><p>注意g4文件所编译代码中的包名,此处演示使用<code>antlr4</code></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.antlr<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>antlr4-maven-plugin<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>${antlr4.version}<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">executions</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">execution</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">goals</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">goal</span>></span>antlr4<span class="tag"></<span class="name">goal</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">goals</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">execution</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">executions</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">configuration</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">visitor</span>></span>true<span class="tag"></<span class="name">visitor</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">arguments</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">argument</span>></span>-package<span class="tag"></<span class="name">argument</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">argument</span>></span>antlr4<span class="tag"></<span class="name">argument</span>></span> //此处填写包名</span><br><span class="line"> <span class="tag"></<span class="name">arguments</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">configuration</span>></span></span><br><span class="line"><span class="tag"></<span class="name">plugin</span>></span></span><br></pre></td></tr></table></figure><h3 id="用于测试的文件"><a href="#用于测试的文件" class="headerlink" title="用于测试的文件"></a>用于测试的文件</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">第一章:这是第一章</span><br><span class="line">第二章:这是第二章</span><br><span class="line">第三章:这是第三章</span><br><span class="line"></span><br><span class="line">1、 这是一级标题</span><br><span class="line">1.2、 这是二级标题</span><br><span class="line">1.2.3、 这是三级标题</span><br></pre></td></tr></table></figure><h3 id="演示代码"><a href="#演示代码" class="headerlink" title="演示代码"></a>演示代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> org.example;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"><span class="keyword">import</span> java.util.Scanner;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> antlr4.TitleDetectBaseVisitor;</span><br><span class="line"><span class="keyword">import</span> antlr4.TitleDetectLexer;</span><br><span class="line"><span class="keyword">import</span> antlr4.TitleDetectParser;</span><br><span class="line"><span class="keyword">import</span> org.antlr.v4.runtime.CharStreams;</span><br><span class="line"><span class="keyword">import</span> org.antlr.v4.runtime.CommonTokenStream;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> <span class="type">Scanner</span> <span class="variable">scanner</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(Main.class.getClassLoader().getResourceAsStream(<span class="string">"input.txt"</span>));</span><br><span class="line"> <span class="keyword">while</span> (scanner.hasNext()) {</span><br><span class="line"> <span class="type">String</span> <span class="variable">input</span> <span class="operator">=</span> scanner.nextLine();</span><br><span class="line"> <span class="type">TitleDetectLexer</span> <span class="variable">lexer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TitleDetectLexer</span>(CharStreams.fromString(input));</span><br><span class="line"> lexer.removeErrorListeners(); <span class="comment">//去除默认的错误处理工具,否则会在stdout中答应错误信息</span></span><br><span class="line"> <span class="type">CommonTokenStream</span> <span class="variable">tokens</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CommonTokenStream</span>(lexer);</span><br><span class="line"> <span class="type">TitleDetectParser</span> <span class="variable">parser</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TitleDetectParser</span>(tokens);</span><br><span class="line"> parser.removeErrorListeners(); <span class="comment">//去除默认的错误处理工具,否则会在stdout中答应错误信息</span></span><br><span class="line"> <span class="type">ResultWrapper</span> <span class="variable">result</span> <span class="operator">=</span> parser.expr().accept(<span class="keyword">new</span> <span class="title class_">TitleDetectVisitorImpl</span>());</span><br><span class="line"> <span class="keyword">if</span> (parser.getNumberOfSyntaxErrors() == <span class="number">0</span></span><br><span class="line"> && !result.level.isEmpty()) {</span><br><span class="line"> System.out.format(<span class="string">"%s : %s\n"</span>, input, result);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> scanner.close();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TitleDetectVisitorImpl</span> <span class="keyword">extends</span> <span class="title class_">TitleDetectBaseVisitor</span><ResultWrapper> {</span><br><span class="line"> Map<String, Integer> cn2int = Map.of(<span class="string">"一"</span>, <span class="number">1</span>, <span class="string">"二"</span>, <span class="number">2</span>, <span class="string">"三"</span>, <span class="number">3</span>, <span class="string">"四"</span>, <span class="number">4</span>, <span class="string">"五"</span>, <span class="number">5</span>, <span class="string">"六"</span>, <span class="number">6</span>, <span class="string">"七"</span>, <span class="number">7</span>, <span class="string">"八"</span>, <span class="number">8</span>, <span class="string">"九"</span>, <span class="number">9</span>, <span class="string">"十"</span>, <span class="number">10</span>);</span><br><span class="line"></span><br><span class="line"> List<Integer> level = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"> <span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> ResultWrapper <span class="title function_">visitTitle_begin</span><span class="params">(TitleDetectParser.Title_beginContext ctx)</span> {</span><br><span class="line"> sb.append(ctx.getText());</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">super</span>.visitTitle_begin(ctx);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> ResultWrapper <span class="title function_">visitSplite</span><span class="params">(TitleDetectParser.SpliteContext ctx)</span> {</span><br><span class="line"> sb.append(ctx.getText());</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">super</span>.visitSplite(ctx);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> ResultWrapper <span class="title function_">visitNum</span><span class="params">(TitleDetectParser.NumContext ctx)</span> {</span><br><span class="line"> sb.append(<span class="string">"num"</span>);</span><br><span class="line"> level.add(Integer.valueOf(ctx.getText()));</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">super</span>.visitNum(ctx);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> ResultWrapper <span class="title function_">visitNum_cn</span><span class="params">(TitleDetectParser.Num_cnContext ctx)</span> {</span><br><span class="line"> sb.append(<span class="string">"num_cn"</span>);</span><br><span class="line"> level.add(cn2int.get(ctx.getText()));</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">super</span>.visitNum_cn(ctx);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> ResultWrapper <span class="title function_">visitMultive_level_num_splite</span><span class="params">(TitleDetectParser.Multive_level_num_spliteContext ctx)</span> {</span><br><span class="line"> sb.append(ctx.getText());</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">super</span>.visitMultive_level_num_splite(ctx);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> ResultWrapper <span class="title function_">defaultResult</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResultWrapper</span>(sb.toString(), level);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ResultWrapper</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">final</span> String patten;</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">final</span> List<Integer> level;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> ResultWrapper(String patten, List<Integer> level) {</span><br><span class="line"> <span class="built_in">this</span>.patten = patten;</span><br><span class="line"> <span class="built_in">this</span>.level = level;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"ResultWrapper{"</span> +</span><br><span class="line"> <span class="string">"patten='"</span> + patten + <span class="string">'\''</span> +</span><br><span class="line"> <span class="string">", level="</span> + level +</span><br><span class="line"> <span class="string">'}'</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="演示结果"><a href="#演示结果" class="headerlink" title="演示结果"></a>演示结果</h3><img src="/2023/%E5%9F%BA%E4%BA%8E%E8%AF%AD%E6%B3%95%E6%A0%91%E5%AF%B9%E6%96%87%E7%AB%A0%E4%B8%AD%E7%9A%84%E7%AB%A0%E8%8A%82%E6%95%B0%E6%8D%AE%E8%BF%9B%E8%A1%8C%E5%8C%B9%E9%85%8D/%E6%BC%94%E7%A4%BA%E7%BB%93%E6%9E%9C.png" class="" title="演示结果"><p>此处拿到了标题的patten,并且可以拿到对应的标题中编号数值及编号层级,在此基础上可以进行对标题层级的还原。</p><p>基于语法树的方式相较基于正则的方式在后续维护过程中更为直观,并且在灵活性和性能上也更高。</p>]]></content>
<tags>
<tag> NLP ANTLR4 JAVA </tag>
</tags>
</entry>
<entry>
<title>hexo将图片发布为网站静态资源</title>
<link href="/2023/hexo%E5%B0%86%E5%9B%BE%E7%89%87%E5%8F%91%E5%B8%83%E4%B8%BA%E7%BD%91%E7%AB%99%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90.html"/>
<url>/2023/hexo%E5%B0%86%E5%9B%BE%E7%89%87%E5%8F%91%E5%B8%83%E4%B8%BA%E7%BD%91%E7%AB%99%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90.html</url>
<content type="html"><![CDATA[<h3 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h3><p>目前部分博客页面所引用的图片存储在图床,存在以下几个问题:</p><ul><li>每个图片需要单独上传,流程复杂,并且本地和图床的图片需要单独管理</li><li>当使用github作为图床时,存在隐私问题,如不小心上传敏感图片时,需要通过<code>push --force</code>等方式覆盖所有提交</li><li>当使用阿里云等对象存储需要付费</li></ul><h3 id="想要实现的目标"><a href="#想要实现的目标" class="headerlink" title="想要实现的目标"></a>想要实现的目标</h3><ul><li>将图片,如png等和html发布为网站内静态资源,从而摆脱图床的限制</li><li>与typora编辑器无缝配合</li><li>将hexo仓库放在github private项目管理,将编译后的结果放在github.io进行展示</li></ul><h3 id="步骤-2027-08-09"><a href="#步骤-2027-08-09" class="headerlink" title="步骤(2027-08-09)"></a>步骤(2027-08-09)</h3><p>今天发现不需要设置<code>typora-root-url</code>,有一个插件可以解决这个问题<br><a href="https://github.com/cocowool/hexo-image-link">https://github.com/cocowool/hexo-image-link</a></p><h3 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h3><h4 id="开启hexo自带的文章资源文件夹功能"><a href="#开启hexo自带的文章资源文件夹功能" class="headerlink" title="开启hexo自带的文章资源文件夹功能"></a>开启hexo自带的<code>文章资源文件夹</code>功能</h4><blockquote><p><a href="https://hexo.io/zh-cn/docs/asset-folders.html#%E6%96%87%E7%AB%A0%E8%B5%84%E6%BA%90%E6%96%87%E4%BB%B6%E5%A4%B9">https://hexo.io/zh-cn/docs/asset-folders.html#%E6%96%87%E7%AB%A0%E8%B5%84%E6%BA%90%E6%96%87%E4%BB%B6%E5%A4%B9</a></p></blockquote><p>当开启这个功能之后,使用 hexo new命令创建新的文章时,_post目录下会生成一个文章的md以及与文章同名的文件夹</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># _config.yml</span></span><br><span class="line"><span class="attr">post_asset_folder:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><img src="/2023/hexo%E5%B0%86%E5%9B%BE%E7%89%87%E5%8F%91%E5%B8%83%E4%B8%BA%E7%BD%91%E7%AB%99%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90/image-20231001041722118.png" class=""><h4 id="使用-Markdown-嵌入图片"><a href="#使用-Markdown-嵌入图片" class="headerlink" title="使用 Markdown 嵌入图片"></a>使用 Markdown 嵌入图片</h4><blockquote><p><a href="https://hexo.io/zh-cn/docs/asset-folders.html#%E4%BD%BF%E7%94%A8-Markdown-%E5%B5%8C%E5%85%A5%E5%9B%BE%E7%89%87">https://hexo.io/zh-cn/docs/asset-folders.html#%E4%BD%BF%E7%94%A8-Markdown-%E5%B5%8C%E5%85%A5%E5%9B%BE%E7%89%87</a></p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># _config.yml</span></span><br><span class="line"><span class="attr">post_asset_folder:</span> <span class="literal">true</span> <span class="comment"># 此处前一步已经添加了</span></span><br><span class="line"><span class="attr">marked:</span></span><br><span class="line"> <span class="attr">prependRoot:</span> <span class="literal">true</span></span><br><span class="line"> <span class="attr">postAsset:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><h4 id="设置typora中插入图片的复制行为"><a href="#设置typora中插入图片的复制行为" class="headerlink" title="设置typora中插入图片的复制行为"></a>设置typora中插入图片的复制行为</h4><p>将插入图片的复制行为改为将图片复制到<code>./${filename}</code>下,并设置图片语法偏好</p><img src="/2023/hexo%E5%B0%86%E5%9B%BE%E7%89%87%E5%8F%91%E5%B8%83%E4%B8%BA%E7%BD%91%E7%AB%99%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90/image-20231001042509010.png" class="" title="image-20231001042509010"><h4 id="设置当前文章中,typora图片的根目录"><a href="#设置当前文章中,typora图片的根目录" class="headerlink" title="设置当前文章中,typora图片的根目录"></a>设置当前文章中,typora图片的根目录</h4><h5 id="方式一"><a href="#方式一" class="headerlink" title="方式一"></a>方式一</h5><img src="/2023/hexo%E5%B0%86%E5%9B%BE%E7%89%87%E5%8F%91%E5%B8%83%E4%B8%BA%E7%BD%91%E7%AB%99%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90/image-20231001042140618.png" class="" title="image-20231001042140618"><h5 id="方式2"><a href="#方式2" class="headerlink" title="方式2"></a>方式2</h5><img src="/2023/hexo%E5%B0%86%E5%9B%BE%E7%89%87%E5%8F%91%E5%B8%83%E4%B8%BA%E7%BD%91%E7%AB%99%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90/image-20231001042818822.png" class="" title="image-20231001042818822"><p>直接在meta data中添加<code>typora-root-url</code>标签</p><h3 id="最终效果"><a href="#最终效果" class="headerlink" title="最终效果"></a>最终效果</h3><img src="/2023/hexo%E5%B0%86%E5%9B%BE%E7%89%87%E5%8F%91%E5%B8%83%E4%B8%BA%E7%BD%91%E7%AB%99%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90/image-20231001042902807.png" class=""><p>可以直接摆脱图床,赞!</p>]]></content>
<tags>
<tag> hexo </tag>
</tags>
</entry>
<entry>
<title>RabbitMQ通过Docker建立集群</title>
<link href="/2022/RabbitMQ%E9%80%9A%E8%BF%87Docker%E5%BB%BA%E7%AB%8B%E9%9B%86%E7%BE%A4.html"/>
<url>/2022/RabbitMQ%E9%80%9A%E8%BF%87Docker%E5%BB%BA%E7%AB%8B%E9%9B%86%E7%BE%A4.html</url>
<content type="html"><![CDATA[<blockquote><p><a href="https://github.com/rabbitmq/rabbitmq-cli/pull/445">https://github.com/rabbitmq/rabbitmq-cli/pull/445</a></p></blockquote><p>目前<code>RABBITMQ_ERLANG_COOKIE</code>环境变量已经被弃用,现在有两种方法设置erlang cookie</p><ul><li>在容器启动时直接覆盖<code>$HOME/.erlang.cookie</code>文件</li><li>使用<code>--erlang-cookie</code>显式覆盖erlang cookie</li></ul><h4 id="直接覆盖-HOME-erlang-cookie文件"><a href="#直接覆盖-HOME-erlang-cookie文件" class="headerlink" title="直接覆盖$HOME/.erlang.cookie文件"></a>直接覆盖$HOME/.erlang.cookie文件</h4><p>在容器启动时直接挂载一个<code>$HOME/.erlang.cookie</code>文件到容器中去</p><ol><li><p>创建一个自定义的erlang.cookie文件,注意修改文件的权限</p><blockquote><p>也可以用docker secret</p></blockquote></li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">自定义erlang.cookie文件内容</span></span><br><span class="line">echo '123456' > erlang.cookie</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">修改文件所有者为容器内的rabbitmq用户,并且修改读写权限为0600</span></span><br><span class="line">chmod 0400 erlang.cookie</span><br><span class="line">chown 999:999 erlang.cookie</span><br></pre></td></tr></table></figure><ol start="2"><li>创建网桥和容器,并把文件挂载到容器内部去</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">创建网桥</span></span><br><span class="line">docker network create --driver=bridge rabbitmq</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">创建容器</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">$(<span class="built_in">pwd</span>)/erlang.cookie代表上一步在当前目录创建的erlang.cookie文件</span></span><br><span class="line">docker run -d --name=rabbitmq-1 --hostname=rabbitmq-1 -v $(pwd)/erlang.cookie:/var/lib/rabbitmq/.erlang.cookie:ro --network=rabbitmq rabbitmq:3.9.15-management</span><br><span class="line">docker run -d --name=rabbitmq-2 --hostname=rabbitmq-2 -v $(pwd)/erlang.cookie:/var/lib/rabbitmq/.erlang.cookie:ro --network=rabbitmq rabbitmq:3.9.15-management</span><br><span class="line">docker run -d --name=rabbitmq-3 --hostname=rabbitmq-3 -v $(pwd)/erlang.cookie:/var/lib/rabbitmq/.erlang.cookie:ro --network=rabbitmq rabbitmq:3.9.15-management</span><br></pre></td></tr></table></figure><ol start="3"><li>建立集群</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">把rabbitmq-2和rabbitmq-3加入集群</span></span><br><span class="line">docker exec -it rabbitmq-2 /bin/bash -c "rabbitmqctl stop_app && rabbitmqctl join_cluster rabbit@rabbitmq-1 && rabbitmqctl start_app"</span><br><span class="line">docker exec -it rabbitmq-3 /bin/bash -c "rabbitmqctl stop_app && rabbitmqctl join_cluster rabbit@rabbitmq-1 && rabbitmqctl start_app"</span><br></pre></td></tr></table></figure><ol start="4"><li>进入rabbitmq-1容器前端管理页面</li></ol><p><img src="https://cdn.jsdelivr.net/gh/codexvn/Images@master/2022/RabbitMQ%E9%80%9A%E8%BF%87Docker%E5%BB%BA%E7%AB%8B%E9%9B%86%E7%BE%A4/20220912220902-843267f3db2e919eed1fb8230dd0e2f3.png"></p><h4 id="使用-erlang-cookie显式覆盖erlang-cookie(最方便)"><a href="#使用-erlang-cookie显式覆盖erlang-cookie(最方便)" class="headerlink" title="使用--erlang-cookie显式覆盖erlang cookie(最方便)"></a>使用<code>--erlang-cookie</code>显式覆盖erlang cookie(最方便)</h4><p>需要用到<code>RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS</code>这个环境变量,假设想要设置的erlang cookie为<code>123456</code></p><ol><li>创建网桥和容器</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">创建网桥</span></span><br><span class="line">docker network create --driver=bridge rabbitmq</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">创建容器</span></span><br><span class="line">docker run -d --name=rabbitmq-1 --hostname=rabbitmq-1 -e RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="-setcookie 123456" --network=rabbitmq rabbitmq:3.9.15-management</span><br><span class="line">docker run -d --name=rabbitmq-2 --hostname=rabbitmq-2 -e RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="-setcookie 123456" --network=rabbitmq rabbitmq:3.9.15-management</span><br><span class="line">docker run -d --name=rabbitmq-3 --hostname=rabbitmq-3 -e RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="-setcookie 123456" --network=rabbitmq rabbitmq:3.9.15-management</span><br></pre></td></tr></table></figure><ol start="2"><li>建立集群</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">需要使用--erlang-cookie显式指定erlang cookie</span></span><br><span class="line">docker exec -it rabbitmq-2 /bin/bash -c "rabbitmqctl stop_app --erlang-cookie '123456' && rabbitmqctl join_cluster rabbit@rabbitmq-1 --erlang-cookie '123456' && rabbitmqctl start_app --erlang-cookie '123456'"</span><br><span class="line">docker exec -it rabbitmq-3 /bin/bash -c "rabbitmqctl stop_app --erlang-cookie '123456' && rabbitmqctl join_cluster rabbit@rabbitmq-1 --erlang-cookie '123456' && rabbitmqctl start_app --erlang-cookie '123456'"</span><br></pre></td></tr></table></figure><ol start="3"><li>进入rabbitmq-1容器前端管理页面</li></ol><p><img src="https://cdn.jsdelivr.net/gh/codexvn/Images@master/2022/RabbitMQ%E9%80%9A%E8%BF%87Docker%E5%BB%BA%E7%AB%8B%E9%9B%86%E7%BE%A4/20220912220902-843267f3db2e919eed1fb8230dd0e2f3.png"></p>]]></content>
</entry>
<entry>
<title>如何设置Java应用的时区</title>
<link href="/2022/%E5%A6%82%E4%BD%95%E8%AE%BE%E7%BD%AEJava%E5%BA%94%E7%94%A8%E7%9A%84%E6%97%B6%E5%8C%BA.html"/>
<url>/2022/%E5%A6%82%E4%BD%95%E8%AE%BE%E7%BD%AEJava%E5%BA%94%E7%94%A8%E7%9A%84%E6%97%B6%E5%8C%BA.html</url>
<content type="html"><![CDATA[<p>最近研究时区问题的时候发现Linux上的Java时区设置简单中带着一些不简单,翻了翻jvm的源码把这部分的逻辑理清楚了</p><h4 id="JVM如何获取获取当前时区"><a href="#JVM如何获取获取当前时区" class="headerlink" title="JVM如何获取获取当前时区"></a>JVM如何获取获取当前时区</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> TimeZone <span class="title function_">getDefault</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> (TimeZone) getDefaultRef().clone();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* Returns the reference to the default TimeZone object. This</span></span><br><span class="line"><span class="comment">* method doesn't create a clone.</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">static</span> TimeZone <span class="title function_">getDefaultRef</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">TimeZone</span> <span class="variable">defaultZone</span> <span class="operator">=</span> defaultTimeZone;</span><br><span class="line"> <span class="keyword">if</span> (defaultZone == <span class="literal">null</span>) {</span><br><span class="line"> <span class="comment">// Need to initialize the default time zone.</span></span><br><span class="line"> defaultZone = setDefaultZone();</span><br><span class="line"> <span class="keyword">assert</span> defaultZone != <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Don't clone here.</span></span><br><span class="line"> <span class="keyword">return</span> defaultZone;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">synchronized</span> TimeZone <span class="title function_">setDefaultZone</span><span class="params">()</span> {</span><br><span class="line"> TimeZone tz;</span><br><span class="line"> <span class="comment">// get the time zone ID from the system properties</span></span><br><span class="line"> <span class="type">Properties</span> <span class="variable">props</span> <span class="operator">=</span> GetPropertyAction.privilegedGetProperties();</span><br><span class="line"> <span class="type">String</span> <span class="variable">zoneID</span> <span class="operator">=</span> props.getProperty(<span class="string">"user.timezone"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// if the time zone ID is not set (yet), perform the</span></span><br><span class="line"> <span class="comment">// platform to Java time zone ID mapping.</span></span><br><span class="line"> <span class="keyword">if</span> (zoneID == <span class="literal">null</span> || zoneID.isEmpty()) {</span><br><span class="line"> <span class="type">String</span> <span class="variable">javaHome</span> <span class="operator">=</span> StaticProperty.javaHome();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> zoneID = getSystemTimeZoneID(javaHome);</span><br><span class="line"> <span class="keyword">if</span> (zoneID == <span class="literal">null</span>) {</span><br><span class="line"> zoneID = GMT_ID;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (NullPointerException e) {</span><br><span class="line"> zoneID = GMT_ID;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Get the time zone for zoneID. But not fall back to</span></span><br><span class="line"> <span class="comment">// "GMT" here.</span></span><br><span class="line"> tz = getTimeZone(zoneID, <span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (tz == <span class="literal">null</span>) {</span><br><span class="line"> <span class="comment">// If the given zone ID is unknown in Java, try to</span></span><br><span class="line"> <span class="comment">// get the GMT-offset-based time zone ID,</span></span><br><span class="line"> <span class="comment">// a.k.a. custom time zone ID (e.g., "GMT-08:00").</span></span><br><span class="line"> <span class="type">String</span> <span class="variable">gmtOffsetID</span> <span class="operator">=</span> getSystemGMTOffsetID();</span><br><span class="line"> <span class="keyword">if</span> (gmtOffsetID != <span class="literal">null</span>) {</span><br><span class="line"> zoneID = gmtOffsetID;</span><br><span class="line"> }</span><br><span class="line"> tz = getTimeZone(zoneID, <span class="literal">true</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">assert</span> tz != <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">id</span> <span class="operator">=</span> zoneID;</span><br><span class="line"> props.setProperty(<span class="string">"user.timezone"</span>, id);</span><br><span class="line"></span><br><span class="line"> defaultTimeZone = tz;</span><br><span class="line"> <span class="keyword">return</span> tz;</span><br><span class="line">}</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* Gets the platform defined TimeZone ID.</span></span><br><span class="line"><span class="comment">**/</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">native</span> String <span class="title function_">getSystemTimeZoneID</span><span class="params">(String javaHome)</span>;</span><br></pre></td></tr></table></figure><p>可以看到获取默认时区的主要代码逻辑在<code>setDefaultZone</code>这个方法中:</p><ol><li>先从<code>user.timezone</code>这个系统参数中获取时区设置</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">TimeZone tz;</span><br><span class="line"><span class="comment">// get the time zone ID from the system properties</span></span><br><span class="line"><span class="type">Properties</span> <span class="variable">props</span> <span class="operator">=</span> GetPropertyAction.privilegedGetProperties();</span><br><span class="line"><span class="type">String</span> <span class="variable">zoneID</span> <span class="operator">=</span> props.getProperty(<span class="string">"user.timezone"</span>);</span><br></pre></td></tr></table></figure><ol start="2"><li>如果<code>user.timezone</code>没有设置,则获取系统的默认时区设置,如果没有获取到默认时区设置则使用<code>GMT</code></li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// if the time zone ID is not set (yet), perform the</span></span><br><span class="line"><span class="comment">// platform to Java time zone ID mapping.</span></span><br><span class="line"><span class="keyword">if</span> (zoneID == <span class="literal">null</span> || zoneID.isEmpty()) {</span><br><span class="line"> <span class="type">String</span> <span class="variable">javaHome</span> <span class="operator">=</span> StaticProperty.javaHome();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> zoneID = getSystemTimeZoneID(javaHome);</span><br><span class="line"> <span class="keyword">if</span> (zoneID == <span class="literal">null</span>) {</span><br><span class="line"> zoneID = GMT_ID;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (NullPointerException e) {</span><br><span class="line"> zoneID = GMT_ID;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="3"><li>如果获取到了系统的默认时区设置,但是无法转换成java的<code>ZoneID</code>,则fallback到<code>GMT</code></li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (tz == <span class="literal">null</span>) {</span><br><span class="line"> <span class="comment">// If the given zone ID is unknown in Java, try to</span></span><br><span class="line"> <span class="comment">// get the GMT-offset-based time zone ID,</span></span><br><span class="line"> <span class="comment">// a.k.a. custom time zone ID (e.g., "GMT-08:00").</span></span><br><span class="line"> <span class="type">String</span> <span class="variable">gmtOffsetID</span> <span class="operator">=</span> getSystemGMTOffsetID();</span><br><span class="line"> <span class="keyword">if</span> (gmtOffsetID != <span class="literal">null</span>) {</span><br><span class="line"> zoneID = gmtOffsetID;</span><br><span class="line"> }</span><br><span class="line"> tz = getTimeZone(zoneID, <span class="literal">true</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="4"><li>最后将时区赋值到<code>user.timezone</code>系统参数里</li></ol><h4 id="getSystemTimeZoneID"><a href="#getSystemTimeZoneID" class="headerlink" title="getSystemTimeZoneID"></a>getSystemTimeZoneID</h4><p><code>user.timezone</code>这个系统参数用的很少,一般对于linux的服务会使用TZ变量来对Java服务进行时区的配置。</p><p><code>getSystemTimeZoneID</code>是一个native函数,可以在<a href="https://github.com/openjdk/jdk/blob/47b86690b6672301aa46d4a7b9ced58d17047cc7/src/java.base/share/native/libjava/TimeZone.c#L40">Github</a>上找到这个<em>函数</em>的源码,这个方法调用了一个<code>findJavaTZ_md</code>函数,这个函数在<code>TimeZone_md.h</code>中被声明,对于不同的操作系统jvm有不同的实现,Linux操作系统上的实现的.h和.c文件地址分别为:</p><ul><li><p><a href="https://github.com/openjdk/jdk/blob/master/src/java.base/unix/native/libjava/TimeZone_md.h">https://github.com/openjdk/jdk/blob/master/src/java.base/unix/native/libjava/TimeZone_md.h</a></p></li><li><p><a href="https://github.com/openjdk/jdk/blob/master/src/java.base/unix/native/libjava/TimeZone_md.c">https://github.com/openjdk/jdk/blob/master/src/java.base/unix/native/libjava/TimeZone_md.c</a></p></li></ul><p><code>findJavaTZ_md</code>这个函数首先会从<code>TZ</code>环境变量拿系统的默认时区,没有获取到有效信息则会调用<code>getPlatformTimeZoneID</code>函数去<code>/etc/timezone</code>和<code>/etc/localtime</code>文件获取系统时区信息</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">char</span> * <span class="title function_">findJavaTZ_md</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *java_home_dir)</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">char</span> *tz;</span><br><span class="line"> <span class="type">char</span> *javatz = <span class="literal">NULL</span>;</span><br><span class="line"> <span class="type">char</span> *freetz = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line"> tz = getenv(<span class="string">"TZ"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (tz == <span class="literal">NULL</span> || *tz == <span class="string">'\0'</span>) {</span><br><span class="line"> tz = getPlatformTimeZoneID();</span><br><span class="line"> freetz = tz;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (tz != <span class="literal">NULL</span>) {</span><br><span class="line"> <span class="comment">/* Ignore preceding ':' */</span></span><br><span class="line"> <span class="keyword">if</span> (*tz == <span class="string">':'</span>) {</span><br><span class="line"> tz++;</span><br><span class="line"> }</span><br><span class="line"><span class="meta">#<span class="keyword">if</span> defined(__linux__)</span></span><br><span class="line"> <span class="comment">/* Ignore "posix/" prefix on Linux. */</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">strncmp</span>(tz, <span class="string">"posix/"</span>, <span class="number">6</span>) == <span class="number">0</span>) {</span><br><span class="line"> tz += <span class="number">6</span>;</span><br><span class="line"> }</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">if</span> defined(_AIX)</span></span><br><span class="line"> <span class="comment">/* On AIX do the platform to Java mapping. */</span></span><br><span class="line"> javatz = mapPlatformToJavaTimezone(java_home_dir, tz);</span><br><span class="line"> <span class="keyword">if</span> (freetz != <span class="literal">NULL</span>) {</span><br><span class="line"> <span class="built_in">free</span>((<span class="type">void</span> *) freetz);</span><br><span class="line"> }</span><br><span class="line"><span class="meta">#<span class="keyword">else</span></span></span><br><span class="line"> <span class="keyword">if</span> (freetz == <span class="literal">NULL</span>) {</span><br><span class="line"> <span class="comment">/* strdup if we are still working on getenv result. */</span></span><br><span class="line"> javatz = strdup(tz);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (freetz != tz) {</span><br><span class="line"> <span class="comment">/* strdup and free the old buffer, if we moved the pointer. */</span></span><br><span class="line"> javatz = strdup(tz);</span><br><span class="line"> <span class="built_in">free</span>((<span class="type">void</span> *) freetz);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">/* we are good if we already work on a freshly allocated buffer. */</span></span><br><span class="line"> javatz = tz;</span><br><span class="line"> }</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> javatz;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">if</span> defined(__linux__) || defined(_ALLBSD_SOURCE)</span></span><br><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="type">char</span> *ETC_TIMEZONE_FILE = <span class="string">"/etc/timezone"</span>;</span><br><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="type">char</span> *ZONEINFO_DIR = <span class="string">"/usr/share/zoneinfo"</span>;</span><br><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="type">char</span> *DEFAULT_ZONEINFO_FILE = <span class="string">"/etc/localtime"</span>;</span><br><span class="line"><span class="meta">#<span class="keyword">else</span></span></span><br><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="type">char</span> *SYS_INIT_FILE = <span class="string">"/etc/default/init"</span>;</span><br><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="type">char</span> *ZONEINFO_DIR = <span class="string">"/usr/share/lib/zoneinfo"</span>;</span><br><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="type">char</span> *DEFAULT_ZONEINFO_FILE = <span class="string">"/usr/share/lib/zoneinfo/localtime"</span>;</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span> <span class="comment">/* defined(__linux__) || defined(_ALLBSD_SOURCE) */</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Performs Linux specific mapping and returns a zone ID</span></span><br><span class="line"><span class="comment"> * if found. Otherwise, NULL is returned.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">static</span> <span class="type">char</span> * <span class="title function_">getPlatformTimeZoneID</span><span class="params">()</span></span><br><span class="line">{</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">stat64</span> <span class="title">statbuf</span>;</span></span><br><span class="line"> <span class="type">char</span> *tz = <span class="literal">NULL</span>;</span><br><span class="line"> FILE *fp;</span><br><span class="line"> <span class="type">int</span> fd;</span><br><span class="line"> <span class="type">char</span> *buf;</span><br><span class="line"> <span class="type">size_t</span> size;</span><br><span class="line"> <span class="type">int</span> res;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">if</span> defined(__linux__)</span></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Try reading the /etc/timezone file for Debian distros. There's</span></span><br><span class="line"><span class="comment"> * no spec of the file format available. This parsing assumes that</span></span><br><span class="line"><span class="comment"> * there's one line of an Olson tzid followed by a '\n', no</span></span><br><span class="line"><span class="comment"> * leading or trailing spaces, no comments./etc/timezone</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">if</span> ((fp = fopen(ETC_TIMEZONE_FILE, <span class="string">"r"</span>)) != <span class="literal">NULL</span>) {</span><br><span class="line"> <span class="type">char</span> line[<span class="number">256</span>];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (fgets(line, <span class="keyword">sizeof</span>(line), fp) != <span class="literal">NULL</span>) {</span><br><span class="line"> <span class="type">char</span> *p = <span class="built_in">strchr</span>(line, <span class="string">'\n'</span>);</span><br><span class="line"> <span class="keyword">if</span> (p != <span class="literal">NULL</span>) {</span><br><span class="line"> *p = <span class="string">'\0'</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">strlen</span>(line) > <span class="number">0</span>) {</span><br><span class="line"> tz = strdup(line);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> (<span class="type">void</span>) fclose(fp);</span><br><span class="line"> <span class="keyword">if</span> (tz != <span class="literal">NULL</span>) {</span><br><span class="line"> <span class="keyword">return</span> tz;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span> <span class="comment">/* defined(__linux__) */</span></span></span><br><span class="line"></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Next, try /etc/localtime to find the zone ID.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> RESTARTABLE(lstat64(DEFAULT_ZONEINFO_FILE, &statbuf), res);</span><br><span class="line"> <span class="keyword">if</span> (res == <span class="number">-1</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * If it's a symlink, get the link name and its zone ID part. (The</span></span><br><span class="line"><span class="comment"> * older versions of timeconfig created a symlink as described in</span></span><br><span class="line"><span class="comment"> * the Red Hat man page. It was changed in 1999 to create a copy</span></span><br><span class="line"><span class="comment"> * of a zoneinfo file. It's no longer possible to get the zone ID</span></span><br><span class="line"><span class="comment"> * from /etc/localtime.)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">if</span> (S_ISLNK(statbuf.st_mode)) {</span><br><span class="line"> <span class="type">char</span> linkbuf[PATH_MAX+<span class="number">1</span>];</span><br><span class="line"> <span class="type">int</span> len;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ((len = readlink(DEFAULT_ZONEINFO_FILE, linkbuf, <span class="keyword">sizeof</span>(linkbuf)<span class="number">-1</span>)) == <span class="number">-1</span>) {</span><br><span class="line"> jio_fprintf(<span class="built_in">stderr</span>, (<span class="type">const</span> <span class="type">char</span> *) <span class="string">"can't get a symlink of %s\n"</span>,</span><br><span class="line"> DEFAULT_ZONEINFO_FILE);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"> linkbuf[len] = <span class="string">'\0'</span>;</span><br><span class="line"> removeDuplicateSlashes(linkbuf);</span><br><span class="line"> collapse(linkbuf);</span><br><span class="line"> tz = getZoneName(linkbuf);</span><br><span class="line"> <span class="keyword">if</span> (tz != <span class="literal">NULL</span>) {</span><br><span class="line"> tz = strdup(tz);</span><br><span class="line"> <span class="keyword">return</span> tz;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * If it's a regular file, we need to find out the same zoneinfo file</span></span><br><span class="line"><span class="comment"> * that has been copied as /etc/localtime.</span></span><br><span class="line"><span class="comment"> * If initial symbolic link resolution failed, we should treat target</span></span><br><span class="line"><span class="comment"> * file as a regular file.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> RESTARTABLE(open(DEFAULT_ZONEINFO_FILE, O_RDONLY), fd);</span><br><span class="line"> <span class="keyword">if</span> (fd == <span class="number">-1</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> RESTARTABLE(fstat64(fd, &statbuf), res);</span><br><span class="line"> <span class="keyword">if</span> (res == <span class="number">-1</span>) {</span><br><span class="line"> (<span class="type">void</span>) close(fd);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"> size = (<span class="type">size_t</span>) statbuf.st_size;</span><br><span class="line"> buf = (<span class="type">char</span> *) <span class="built_in">malloc</span>(size);</span><br><span class="line"> <span class="keyword">if</span> (buf == <span class="literal">NULL</span>) {</span><br><span class="line"> (<span class="type">void</span>) close(fd);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> RESTARTABLE(read(fd, buf, size), res);</span><br><span class="line"> <span class="keyword">if</span> (res != (<span class="type">ssize_t</span>) size) {</span><br><span class="line"> (<span class="type">void</span>) close(fd);</span><br><span class="line"> <span class="built_in">free</span>((<span class="type">void</span> *) buf);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"> (<span class="type">void</span>) close(fd);</span><br><span class="line"></span><br><span class="line"> tz = findZoneinfoFile(buf, size, ZONEINFO_DIR);</span><br><span class="line"> <span class="built_in">free</span>((<span class="type">void</span> *) buf);</span><br><span class="line"> <span class="keyword">return</span> tz;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="etc-timezone"><a href="#etc-timezone" class="headerlink" title="/etc/timezone"></a>/etc/timezone</h4><p><code>glibc</code>默认的时区配置文件为<code>/etc/localtime</code>,但是Debian系的发行版中还有一些软件在使用<code>/etc/timezone</code>,可以在Debian的软件仓库中找到:</p><p><a href="https://codesearch.debian.net/search?q=/etc/timezone">https://codesearch.debian.net/search?q=%2Fetc%2Ftimezone</a></p><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>所以对于Java服务来说,先读取<code>user.timezone</code>获取时区信息,如果没有获取到<code>user.timezone</code>则读取<code>TZ </code>环境变量,如果<code>TZ</code>环境变量如果没有获取到则读取<code>/etc/timezone</code>和<code>/etc/localtime</code>,如果还是没有读取到则fallback到<code>GMT</code>,最后将获取的的时区信息设置到<code>user.timezone</code>,下次直接从<code>user.timezone</code>获取时区信息。</p><blockquote><p><a href="https://cloud.tencent.com/developer/article/1691540">https://cloud.tencent.com/developer/article/1691540</a></p><p><a href="https://github.com/openjdk/jdk/blob/47b86690b6672301aa46d4a7b9ced58d17047cc7/src/java.base/share/native/libjava/TimeZone.c#L40">https://github.com/openjdk/jdk/blob/47b86690b6672301aa46d4a7b9ced58d17047cc7/src/java.base/share/native/libjava/TimeZone.c#L40</a></p><p><a href="https://github.com/openjdk/jdk/blob/master/src/java.base/unix/native/libjava/TimeZone_md.h">https://github.com/openjdk/jdk/blob/master/src/java.base/unix/native/libjava/TimeZone_md.h</a></p><p><a href="https://github.com/openjdk/jdk/blob/master/src/java.base/unix/native/libjava/TimeZone_md.c">https://github.com/openjdk/jdk/blob/master/src/java.base/unix/native/libjava/TimeZone_md.c</a></p><p><a href="https://codesearch.debian.net/search?q=/etc/timezone">https://codesearch.debian.net/search?q=%2Fetc%2Ftimezone</a></p></blockquote>]]></content>
<tags>
<tag> Java </tag>
<tag> TZ </tag>
<tag> java </tag>
</tags>
</entry>
<entry>
<title>Linux上的TZ环境变量</title>
<link href="/2022/Linux%E4%B8%8A%E7%9A%84TZ%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F.html"/>
<url>/2022/Linux%E4%B8%8A%E7%9A%84TZ%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F.html</url>
<content type="html"><![CDATA[<p>Linux默认的时区是UTC,所以在创建Docker容器时我们会添加一个环境变量<code>TZ=Asia/Shanghai</code>来指定容器的时区,但是偶然发现直接指定TZ环境变量为Asia/Shanghai后<code>date</code>命令输出的时间依然是UTC时间,于是去研究了一下这个环境变量。</p><h4 id="TZ环境变量如何使用"><a href="#TZ环境变量如何使用" class="headerlink" title="TZ环境变量如何使用"></a><code>TZ</code>环境变量如何使用</h4><p><code>TZ</code>是POSIX标准中定义的一个标准环境变量,用来覆盖各种与时间有关的函数的默认时区。在Linux中,基本是<code>GNU C 库</code>即<code>glibc</code>,<code>glibc</code>的<code>TZ</code>环境变量有2种写法:</p><ol><li><code>std offset[dst[offset][,start[/time],end[/time]]]</code>,如国内的时区对应为<code>CTS-8</code></li><li><code>:[filespec]</code>,这是最常见的一种,如<code>Asia/Shanghai</code></li></ol><p>对于<code>TZ</code>环境变量的解释为:</p><ul><li><p>如果使用的filespec格式,对于<code>:/</code>开头的,会去读取指定路径的<code>tzfile</code>格式的时区文件来读取时区信息;对于<code>:</code>开头的会去系统时区目录读取时区文件,即<code>/usr/share/zoneinfo/</code>;对于<code>:</code>也没有,只有一个filespec的,比如常见的<code>TZ=Asia/Shanghai</code>,会尝试上面两种<code>TZ</code>环境变量的格式</p></li><li><p>如果<code>TZ</code>环境变量没有被设置,则使用默认时区,默认时区<code>TZ=:/etc/localtime </code>,通过将<code>tzfile</code>格式的文件复制或者软连接到<code>/etc/localtime</code>来配置默认时区,比如:<code>ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime</code></p></li><li><p>如果TZ环境变量被设置了但是内容为空,或者设置了但是无法被解析,则使用UTC</p></li></ul><h4 id="不是每个镜像都打包了tzfile时区文件"><a href="#不是每个镜像都打包了tzfile时区文件" class="headerlink" title="不是每个镜像都打包了tzfile时区文件"></a>不是每个镜像都打包了<code>tzfile</code>时区文件</h4><p>对于一些特殊的镜像,不一定会带时区文件,可以用以下命令测试</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run --rm 镜像名 ls -lah /usr/share/zoneinfo/Asia/Shanghai</span><br></pre></td></tr></table></figure><p>带时区文件的常见的,比如</p><ul><li>mysql</li></ul><p><img src="https://cdn.jsdelivr.net/gh/codexvn/Images@master/2022/Linux%E4%B8%8A%E7%9A%84TZ%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F/20220618191318-bacdfdf9d0f81232839dec5581a40b7e.png"></p><ul><li>redis</li></ul><p><img src="https://cdn.jsdelivr.net/gh/codexvn/Images@master/2022/Linux%E4%B8%8A%E7%9A%84TZ%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F/20220618191330-d315a9247e2c15d6cb9c158256ce2044.png"></p><p>也有一些不带的,比如</p><ul><li>alpine</li></ul><p><img src="https://cdn.jsdelivr.net/gh/codexvn/Images@master/2022/Linux%E4%B8%8A%E7%9A%84TZ%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F/20220618191334-e018220f4e33b12cb5c19b4742162a1f.png"></p><p>所以为了保证<code>TZ</code>环境变量能够生效,使用<code>CST-8</code>这种会好一些</p><p><img src="https://cdn.jsdelivr.net/gh/codexvn/Images@master/2022/Linux%E4%B8%8A%E7%9A%84TZ%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F/20220618191338-2f1b18bff7822e0503559bc73f4bfadc.png"></p><blockquote><p><a href="https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html">https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html</a></p><p><a href="https://xy2401.com/local-docs/gnu/manual.zh/libc/TZ-Variable.html">https://xy2401.com/local-docs/gnu/manual.zh/libc/TZ-Variable.html</a></p><p><a href="https://man.archlinux.org/man/tzset.3">https://man.archlinux.org/man/tzset.3</a></p><p><a href="https://codesearch.debian.net/search?q=/etc/timezone">https://codesearch.debian.net/search?q=%2Fetc%2Ftimezone</a></p></blockquote>]]></content>
<tags>
<tag> TZ </tag>
</tags>
</entry>
<entry>
<title>EasyExcel与@Accessors,@Builder与@SuperBuilder</title>
<link href="/2022/EazyExcel%E4%B8%8E@Accessors,@Builder%E4%B8%[email protected]"/>
<url>/2022/EazyExcel%E4%B8%8E@Accessors,@Builder%E4%B8%[email protected]</url>
<content type="html"><![CDATA[<p>在使用EasyExcel时发现在解析Excel文件时,发现单元格中的数据无法被注入到对象中,随后去EasyExcel项目的issues区发现这是个老问题了:<a href="https://github.com/alibaba/easyexcel/issues?q=Accessors">https://github.com/alibaba/easyexcel/issues?q=Accessors</a></p><h3 id="原因分析"><a href="#原因分析" class="headerlink" title="原因分析"></a>原因分析</h3><p>在接收Excel数据的对象的构造方法上打断点,并查看方法的调用链可以看到这个方法:</p><img src="/2022/EazyExcel与@Accessors,@Builder与@SuperBuilder.html" style="zoom:50%;" /><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> Object <span class="title function_">buildUserModel</span><span class="params">(Map<Integer, ReadCellData<?>> cellDataMap, ReadSheetHolder readSheetHolder,</span></span><br><span class="line"><span class="params"> AnalysisContext context)</span> {</span><br><span class="line"> <span class="type">ExcelReadHeadProperty</span> <span class="variable">excelReadHeadProperty</span> <span class="operator">=</span> readSheetHolder.excelReadHeadProperty();</span><br><span class="line"> Object resultModel;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> resultModel = excelReadHeadProperty.getHeadClazz().newInstance();</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ExcelDataConvertException</span>(context.readRowHolder().getRowIndex(), <span class="number">0</span>,</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">ReadCellData</span><>(CellDataTypeEnum.EMPTY), <span class="literal">null</span>,</span><br><span class="line"> <span class="string">"Can not instance class: "</span> + excelReadHeadProperty.getHeadClazz().getName(), e);</span><br><span class="line"> }</span><br><span class="line"> Map<Integer, Head> headMap = excelReadHeadProperty.getHeadMap();</span><br><span class="line"> <span class="type">BeanMap</span> <span class="variable">dataMap</span> <span class="operator">=</span> BeanMapUtils.create(resultModel);</span><br><span class="line"> <span class="keyword">for</span> (Map.Entry<Integer, Head> entry : headMap.entrySet()) {</span><br><span class="line"> <span class="type">Integer</span> <span class="variable">index</span> <span class="operator">=</span> entry.getKey();</span><br><span class="line"> <span class="type">Head</span> <span class="variable">head</span> <span class="operator">=</span> entry.getValue();</span><br><span class="line"> <span class="type">String</span> <span class="variable">fieldName</span> <span class="operator">=</span> head.getFieldName();</span><br><span class="line"> <span class="keyword">if</span> (!cellDataMap.containsKey(index)) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> ReadCellData<?> cellData = cellDataMap.get(index);</span><br><span class="line"> <span class="type">Object</span> <span class="variable">value</span> <span class="operator">=</span> ConverterUtils.convertToJavaObject(cellData, head.getField(),</span><br><span class="line"> ClassUtils.declaredExcelContentProperty(dataMap, readSheetHolder.excelReadHeadProperty().getHeadClazz(),</span><br><span class="line"> fieldName), readSheetHolder.converterMap(), context, context.readRowHolder().getRowIndex(), index);</span><br><span class="line"> <span class="keyword">if</span> (value != <span class="literal">null</span>) {</span><br><span class="line"> dataMap.put(fieldName, value);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> resultModel;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以看到EasyExcel是使用cglib的BeanMap进行对象的属性进行赋值:<code> dataMap.put(fieldName, value)</code>,问题就出现在生成的这个BeanMap对象上。</p><h3 id="BeanMap为什么不能和-Accessors-chain-true-一起使用"><a href="#BeanMap为什么不能和-Accessors-chain-true-一起使用" class="headerlink" title="BeanMap为什么不能和@Accessors(chain = true)一起使用"></a>BeanMap为什么不能和@Accessors(chain = true)一起使用</h3><p>BeanMap是一个抽象类,会由cglib生成具体的代理类。假设我们有这么一个类:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Item</span> {</span><br><span class="line"> <span class="meta">@ExcelProperty(index = 0)</span></span><br><span class="line"> <span class="keyword">private</span> String returnThis;</span><br><span class="line"> <span class="meta">@ExcelProperty(index = 1)</span></span><br><span class="line"> <span class="keyword">private</span> String returnVoid;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> Item <span class="title function_">setReturnThis</span><span class="params">(String returnThis)</span> {</span><br><span class="line"> <span class="built_in">this</span>.returnThis = returnThis;</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setReturnVoid</span><span class="params">(String returnVoid)</span> {</span><br><span class="line"> <span class="built_in">this</span>.returnVoid = returnVoid;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p><code>@Accessors(chain = true)</code>的作用就是对于lombok生成的setter方法,返回值不再是void而是对象自己即返回<code>this</code>,</p><p>这样做的好处是使得setter方法支持链式调用,能够方便的在一行代码中同时对多个属性进行赋值,<code>@Builder</code>也能实现这个功能,但是会多生成一个内部静态类。</p></blockquote><p>在类中的两个setter方法中打上断点,再次运行导入excel的方法,会发现返回<code>void</code>的set方法被调用了,而返回<code>this</code>的set方法没有被调用,猜想是cglib生成的BeanMap代理类有什么问题。</p><img src="/2022/EazyExcel与@Accessors,@Builder与@SuperBuilder.html" style="zoom:80%;" /><p>我们把cglib生成的类存到本地看看,设置<code>cglib.debugLocation</code>即可:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span>{</span><br><span class="line"> System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, <span class="string">"/tmp/cglib"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>cglib类是在运行时生成的,所以我们使用static代码块对属性进行设置。</p></blockquote><p>我们在<code>com.alibaba.excel.read.listener.ModelBuildEventListener#buildUserModel</code>这个方法上打上断点,方便我们定位生成的<code>BeanMap</code>类的类文件名:</p><img src="/2022/EazyExcel与@Accessors,@Builder与@SuperBuilder.html" style="zoom: 50%;" /><p>可以很直观的看到是没有<code>returnThis</code>这个key的,并且我们知道了生成的cglib类文件保存为了<code>Item$$BeanMapByEasyExcelCGLIB$$94beaa50.class</code>,我们用Idea打开这个文件看看:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Item$$BeanMapByEasyExcelCGLIB$$94beaa50</span> <span class="keyword">extends</span> <span class="title class_">BeanMap</span> {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> FixedKeySet keys;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Class CGLIB$load_class$java$2Elang$2EString;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> Item$$BeanMapByEasyExcelCGLIB$$94beaa50() {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> BeanMap <span class="title function_">newInstance</span><span class="params">(Object var1)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Item$$BeanMapByEasyExcelCGLIB$$94beaa50</span>(var1);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> Item$$BeanMapByEasyExcelCGLIB$$94beaa50(Object var1) {</span><br><span class="line"> <span class="built_in">super</span>(var1);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> Object <span class="title function_">get</span><span class="params">(Object var1, Object var2)</span> {</span><br><span class="line"> <span class="type">Item</span> <span class="variable">var10000</span> <span class="operator">=</span> (Item)var1;</span><br><span class="line"> ((String)var2).hashCode();</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> Object <span class="title function_">put</span><span class="params">(Object var1, Object var2, Object var3)</span> {</span><br><span class="line"> <span class="type">Item</span> <span class="variable">var10000</span> <span class="operator">=</span> (Item)var1;</span><br><span class="line"> <span class="type">String</span> <span class="variable">var10001</span> <span class="operator">=</span> (String)var2;</span><br><span class="line"> <span class="keyword">switch</span> (((String)var2).hashCode()) {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">1337256676</span>:</span><br><span class="line"> <span class="keyword">if</span> (var10001.equals(<span class="string">"returnVoid"</span>)) {</span><br><span class="line"> var10000.setReturnVoid((String)var3);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> {</span><br><span class="line"> CGLIB$STATICHOOK1();</span><br><span class="line"> keys = <span class="keyword">new</span> <span class="title class_">FixedKeySet</span>(<span class="keyword">new</span> <span class="title class_">String</span>[]{<span class="string">"returnVoid"</span>});</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">void</span> CGLIB$STATICHOOK1() {</span><br><span class="line"> CGLIB$load_class$java$2Elang$2EString = Class.forName(<span class="string">"java.lang.String"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> Set <span class="title function_">keySet</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> keys;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> Class <span class="title function_">getPropertyType</span><span class="params">(String var1)</span> {</span><br><span class="line"> <span class="keyword">switch</span> (var1.hashCode()) {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">1337256676</span>:</span><br><span class="line"> <span class="keyword">if</span> (var1.equals(<span class="string">"returnVoid"</span>)) {</span><br><span class="line"> <span class="keyword">return</span> CGLIB$load_class$java$2Elang$2EString;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们可以看到以下两个特殊的地方:</p><ol><li>对于keySet方法,是直接返回了一个static成员<code>keys</code>的值,<code>keys</code>在静态代码块中被初始化,并且只有<code>returnVoid</code>没有<code>returnThis</code>。</li><li>对于put方法,switch代码块中只有<code>returnVoid</code>相关的代码</li></ol><h3 id="问题解决的办法"><a href="#问题解决的办法" class="headerlink" title="问题解决的办法"></a>问题解决的办法</h3><p>问题解决的办法很简单,只要不使用<code>@Accessors(chain = true)</code>即可,想要链式初始化对象的数据可以使用<code>@Builder</code>或者<code>@SuperBuilder</code>。</p><h3 id="使用-Builder有什么需要注意的地方么?"><a href="#使用-Builder有什么需要注意的地方么?" class="headerlink" title="使用@Builder有什么需要注意的地方么?"></a>使用@Builder有什么需要注意的地方么?</h3><p><strong>构造器(Builder)模式</strong>属于创建型设计模式之一,能够帮我们简化对于复杂对象的初始化,<code>@Builder</code>注解能够自动帮我们完成构造器模式的代码实现,看这段介绍一定会觉得lombok真的是太完美了,帮我们简化了非常多的代码,lombok确实很方便,但是有些地方我们需要注意一下。</p><p>依然是<code>Item</code>类,我们加上<code>@Builder</code>注解,并给每个属性一个初始的值:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@Builder</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Item</span> {</span><br><span class="line"> <span class="meta">@ExcelProperty(index = 0)</span></span><br><span class="line"> <span class="keyword">private</span> String returnThis=<span class="string">"this"</span>;</span><br><span class="line"> <span class="meta">@ExcelProperty(index = 1)</span></span><br><span class="line"> <span class="keyword">private</span> String returnVoid=<span class="string">"void"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后用Idea打开编译出来的.class文件,会发现lombok帮我们自动生成了一个Builder静态内部类:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">ItemBuilder</span> {</span><br><span class="line"> <span class="keyword">private</span> String returnThis;</span><br><span class="line"> <span class="keyword">private</span> String returnVoid;</span><br><span class="line"></span><br><span class="line"> ItemBuilder() {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> ItemBuilder <span class="title function_">returnThis</span><span class="params">(<span class="keyword">final</span> String returnThis)</span> {</span><br><span class="line"> <span class="built_in">this</span>.returnThis = returnThis;</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> ItemBuilder <span class="title function_">returnVoid</span><span class="params">(<span class="keyword">final</span> String returnVoid)</span> {</span><br><span class="line"> <span class="built_in">this</span>.returnVoid = returnVoid;</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> Item <span class="title function_">build</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Item</span>(<span class="built_in">this</span>.returnThis, <span class="built_in">this</span>.returnVoid);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Item.ItemBuilder(returnThis="</span> + <span class="built_in">this</span>.returnThis + <span class="string">", returnVoid="</span> + <span class="built_in">this</span>.returnVoid + <span class="string">")"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以很直观的看出<code>@Builder</code>帮我们做了什么,在加上这个注解后,编译生成的.class文件中会加入一个ItemBuilder类,这个类的属性的Item属性一模一样,唯一的区别就是属性值没有默认值了,<strong>这就会导致我们build出来的对象的属性在没有指定值时都为null</strong>。</p><p>能够解决这个问题么?可以</p><ol><li>使用<code>@Builder(toBuilder = true)</code>即可,查看编译生成的.class文件时能够看到多了这样的代码:</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ItemBuilder <span class="title function_">toBuilder</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">new</span> <span class="title class_">ItemBuilder</span>()).returnThis(<span class="built_in">this</span>.returnThis).returnVoid(<span class="built_in">this</span>.returnVoid);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>相应的我们的使用方法要做出改变,不能这么使用:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Item.builder().xxx.build()</span><br></pre></td></tr></table></figure><p>而是应该这么使用:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Item</span>().toBuilder().xxx.build();</span><br></pre></td></tr></table></figure><ol start="2"><li>在有默认值的属性上加<code>@Builder.Default</code>注解</li></ol><h3 id="Builder和-SuperBuilder有什么区别"><a href="#Builder和-SuperBuilder有什么区别" class="headerlink" title="@Builder和@SuperBuilder有什么区别"></a>@Builder和@SuperBuilder有什么区别</h3><p>通常我们会定义一个父类,将一些通用的属性抽出来,然后子类直接继承这个父类,就不需要重新定义重复的属性,但是对于<code>@Builder</code>注解我们会发现,子类的Builder类中是没有办法初始化到父类的属性的,想要实现Builder能够初始化父类的属性,可以使用<code>@SuperBuilder</code>。</p><p>我们来看看<code>@SuperBuilder</code>做了什么,先来定义两个类Child和Father:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@EqualsAndHashCode(callSuper = true)</span></span><br><span class="line"><span class="meta">@SuperBuilder</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Child</span> <span class="keyword">extends</span> <span class="title class_">Father</span> {</span><br><span class="line"> <span class="keyword">private</span> String childField1;</span><br><span class="line"> <span class="keyword">private</span> String childField2;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@SuperBuilder</span></span><br><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">Father</span> {</span><br><span class="line"> <span class="keyword">private</span> String fatherField1;</span><br><span class="line"> <span class="keyword">private</span> String fatherField2;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>查看编译生成的.class文件可以看到以下代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//Builder抽象静态内部抽象类</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">FatherBuilder</span><C <span class="keyword">extends</span> <span class="title class_">Father</span>, B <span class="keyword">extends</span> <span class="title class_">FatherBuilder</span><C, B>> {</span><br><span class="line"> <span class="keyword">private</span> String fatherField1;</span><br><span class="line"> <span class="keyword">private</span> String fatherField2;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">FatherBuilder</span><span class="params">()</span> {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">abstract</span> B <span class="title function_">self</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">abstract</span> C <span class="title function_">build</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> B <span class="title function_">fatherField1</span><span class="params">(<span class="keyword">final</span> String fatherField1)</span> {</span><br><span class="line"> <span class="built_in">this</span>.fatherField1 = fatherField1;</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>.self();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> B <span class="title function_">fatherField2</span><span class="params">(<span class="keyword">final</span> String fatherField2)</span> {</span><br><span class="line"> <span class="built_in">this</span>.fatherField2 = fatherField2;</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>.self();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Father.FatherBuilder(fatherField1="</span> + <span class="built_in">this</span>.fatherField1 + <span class="string">", fatherField2="</span> + <span class="built_in">this</span>.fatherField2 + <span class="string">")"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">//新的构造器</span></span><br><span class="line"><span class="keyword">protected</span> <span class="title function_">Father</span><span class="params">(<span class="keyword">final</span> FatherBuilder<?, ?> b)</span> {</span><br><span class="line"> <span class="built_in">this</span>.fatherField1 = b.fatherField1;</span><br><span class="line"> <span class="built_in">this</span>.fatherField2 = b.fatherField2;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//Builde静态内部抽象类</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">ChildBuilder</span><C <span class="keyword">extends</span> <span class="title class_">Child</span>, B <span class="keyword">extends</span> <span class="title class_">ChildBuilder</span><C, B>> <span class="keyword">extends</span> <span class="title class_">Father</span>.FatherBuilder<C, B> {</span><br><span class="line"> <span class="keyword">private</span> String childField1;</span><br><span class="line"> <span class="keyword">private</span> String childField2;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">ChildBuilder</span><span class="params">()</span> {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">abstract</span> B <span class="title function_">self</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">abstract</span> C <span class="title function_">build</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> B <span class="title function_">childField1</span><span class="params">(<span class="keyword">final</span> String childField1)</span> {</span><br><span class="line"> <span class="built_in">this</span>.childField1 = childField1;</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>.self();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> B <span class="title function_">childField2</span><span class="params">(<span class="keyword">final</span> String childField2)</span> {</span><br><span class="line"> <span class="built_in">this</span>.childField2 = childField2;</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>.self();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">String</span> <span class="variable">var10000</span> <span class="operator">=</span> <span class="built_in">super</span>.toString();</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Child.ChildBuilder(super="</span> + var10000 + <span class="string">", childField1="</span> + <span class="built_in">this</span>.childField1 + <span class="string">", childField2="</span> + <span class="built_in">this</span>.childField2 + <span class="string">")"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">//Builder静态内部实现类</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ChildBuilderImpl</span> <span class="keyword">extends</span> <span class="title class_">ChildBuilder</span><Child, ChildBuilderImpl> {</span><br><span class="line"> <span class="keyword">private</span> <span class="title function_">ChildBuilderImpl</span><span class="params">()</span> {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">protected</span> ChildBuilderImpl <span class="title function_">self</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> Child <span class="title function_">build</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Child</span>(<span class="built_in">this</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">//新的构造器</span></span><br><span class="line"><span class="keyword">protected</span> <span class="title function_">Child</span><span class="params">(<span class="keyword">final</span> ChildBuilder<?, ?> b)</span> {</span><br><span class="line"> <span class="built_in">super</span>(b);</span><br><span class="line"> <span class="built_in">this</span>.childField1 = b.childField1;</span><br><span class="line"> <span class="built_in">this</span>.childField2 = b.childField2;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ChildBuilder<?, ?> builder() {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ChildBuilderImpl</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>与<code>@Builder</code>不同,<code>@SuperBuilder</code>生成了一个抽象类和一个实现类,并且抽象类是泛型的,根据被注解的类的继承关系这个Builder抽象类会继承所有父类的Builder抽象类,最后实现类实现重现了继承关系的Builder抽象类。</p><p>此时这个实现类即使是初始化父类的属性,初始化方法返回的对象类型也是实现类的类型。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Child.builder()</span><br><span class="line"> .fatherField1(<span class="string">"fatherField1"</span>)</span><br><span class="line"> .childField1(<span class="string">"childField1"</span>)</span><br><span class="line"> .fatherField2(<span class="string">"fatherField2"</span>)</span><br><span class="line"> .childField2(<span class="string">"childField2"</span>);</span><br></pre></td></tr></table></figure><p><code>@SuperBuilder</code>同样支持<code>toBuilder = true</code>和<code>@Builder.Default</code></p><blockquote><p><a href="https://developer.aliyun.com/article/744593">https://developer.aliyun.com/article/744593</a></p><p><a href="https://github.com/spring-projects/spring-framework/issues/27802">https://github.com/spring-projects/spring-framework/issues/27802</a></p><p><a href="https://github.com/spring-projects/spring-framework/issues/28110">https://github.com/spring-projects/spring-framework/issues/28110</a></p><p><a href="https://blog.csdn.net/john1337/article/details/84653468">https://blog.csdn.net/john1337/article/details/84653468</a></p></blockquote>]]></content>
<tags>
<tag> EasyExcel </tag>
<tag> lombok </tag>
<tag> \@Accessors </tag>
<tag> \@Builder </tag>
<tag> \@SuperBuilder </tag>
</tags>
</entry>
<entry>
<title>判断当前环境是否运行在docker中</title>
<link href="/2022/%E5%88%A4%E6%96%AD%E5%BD%93%E5%89%8D%E7%8E%AF%E5%A2%83%E6%98%AF%E5%90%A6%E8%BF%90%E8%A1%8C%E5%9C%A8docker%E4%B8%AD.html"/>
<url>/2022/%E5%88%A4%E6%96%AD%E5%BD%93%E5%89%8D%E7%8E%AF%E5%A2%83%E6%98%AF%E5%90%A6%E8%BF%90%E8%A1%8C%E5%9C%A8docker%E4%B8%AD.html</url>
<content type="html"><![CDATA[<p>有时候我们并不希望我们的服务跑在docker容器中,网上搜索了一下,找到了一种判断当前服务是否运行在docker中的方法</p><h5 id="方法的原理"><a href="#方法的原理" class="headerlink" title="方法的原理"></a>方法的原理</h5><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat /proc/1/sched</span><br></pre></td></tr></table></figure><ul><li><p>如果返回的结果为:<code>systemd (1, #threads: 1)</code>,则说明环境为Linux;对于使用init的系统pid为1的进程为init</p></li><li><p>如果是在docker容器内的话,以java服务为例:<code>java (118925, #threads: 236)</code>,此时pid为1的进程为java</p></li></ul><h5 id="使用方法-以Java为例"><a href="#使用方法-以Java为例" class="headerlink" title="使用方法(以Java为例)"></a>使用方法(以Java为例)</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ProcessBuilder</span> <span class="variable">pb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ProcessBuilder</span>();</span><br><span class="line">pb.command(<span class="string">"cat"</span>, <span class="string">"/proc/1/sched"</span>);</span><br><span class="line"><span class="type">Process</span> <span class="variable">start</span> <span class="operator">=</span> pb.start();</span><br><span class="line"><span class="keyword">try</span> (<span class="type">Scanner</span> <span class="variable">sc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(start.getInputStream())){</span><br><span class="line"> <span class="type">String</span> <span class="variable">pid1</span> <span class="operator">=</span> sc.nextLine();</span><br><span class="line"> <span class="type">Pattern</span> <span class="variable">pattern</span> <span class="operator">=</span> Pattern.compile(<span class="string">"\\b(systemd|init)(?=\\s\\()"</span>);</span><br><span class="line"> <span class="type">boolean</span> <span class="variable">notInContainer</span> <span class="operator">=</span> pattern.matcher(pid1).find();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag> docker </tag>
<tag> Docker </tag>
</tags>
</entry>
<entry>
<title>SpringCloudAlibaba文档汇总</title>
<link href="/2022/SpringCloudAlibaba%E6%96%87%E6%A1%A3%E6%B1%87%E6%80%BB.html"/>
<url>/2022/SpringCloudAlibaba%E6%96%87%E6%A1%A3%E6%B1%87%E6%80%BB.html</url>
<content type="html"><![CDATA[<h5 id="Sentinel"><a href="#Sentinel" class="headerlink" title="Sentinel"></a>Sentinel</h5><p><a href="https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D">wiki</a></p><p><a href="https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81">注解支持</a></p><h5 id="Seata"><a href="#Seata" class="headerlink" title="Seata"></a>Seata</h5><p>seata.io/zh-cn/docs/ops/deploy-guide-beginner.html</p>]]></content>
</entry>
<entry>
<title>JDK下载地址接口</title>
<link href="/2021/JDK%E4%B8%8B%E8%BD%BD%E5%9C%B0%E5%9D%80%E6%8E%A5%E5%8F%A3.html"/>
<url>/2021/JDK%E4%B8%8B%E8%BD%BD%E5%9C%B0%E5%9D%80%E6%8E%A5%E5%8F%A3.html</url>
<content type="html"><![CDATA[<p>在Idea上看到这个东西</p><p><img src="https://cdn.jsdelivr.net/gh/codexvn/Images@master/2021/JDK%E4%B8%8B%E8%BD%BD%E5%9C%B0%E5%9D%80%E6%8E%A5%E5%8F%A3/20211224195529-a0c9bf768e24b50e1a493bb1ec641339.png" alt="image-20211224195527289"></p><p>然后把接口地址抓了出来</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://download.jetbrains.com/jdk/feed/v1/jdks.json.xz</span><br></pre></td></tr></table></figure><hr><p>更新:</p><p>发现接口末尾可以不加.xz,能直接拿到json</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://download.jetbrains.com/jdk/feed/v1/jdks.json</span><br></pre></td></tr></table></figure><p>Have Fun!</p>]]></content>
<tags>
<tag> JDK </tag>
</tags>
</entry>
<entry>
<title>使用Fiddler对Java程序进行抓包</title>
<link href="/2021/%E4%BD%BF%E7%94%A8Fiddler%E5%AF%B9Java%E7%A8%8B%E5%BA%8F%E8%BF%9B%E8%A1%8C%E6%8A%93%E5%8C%85.html"/>
<url>/2021/%E4%BD%BF%E7%94%A8Fiddler%E5%AF%B9Java%E7%A8%8B%E5%BA%8F%E8%BF%9B%E8%A1%8C%E6%8A%93%E5%8C%85.html</url>
<content type="html"><![CDATA[<p>Fiddler是常用的一个抓包工具,可以通过这个工具对Java程序的进行抓包。</p><ol><li><p>第一步是设置代理</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> {</span><br><span class="line"> System.setProperty(<span class="string">"proxyHost"</span>, <span class="string">"localhost"</span>);</span><br><span class="line"> System.setProperty(<span class="string">"proxyPort"</span>, <span class="string">"8888"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>或者添加JVM参数:<code>-DproxyHost=localhost -DproxyPort=8888</code></p><blockquote><p>网上很多教程是将Http的代理和Https的代理分开,实际上通过<code>proxyPort</code>和<code>proxyHost</code>两个属性能够一次性设置Http的代理和Https的代理。</p><p>分析源码中的<code>DefaultProxySelector</code>类可以看到这段话:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * This is where we define all the valid System Properties we have to</span></span><br><span class="line"><span class="comment"> * support for each given protocol.</span></span><br><span class="line"><span class="comment"> * The format of this 2 dimensional array is :</span></span><br><span class="line"><span class="comment"> * - 1 row per protocol (http, ftp, ...)</span></span><br><span class="line"><span class="comment"> * - 1st element of each row is the protocol name</span></span><br><span class="line"><span class="comment"> * - subsequent elements are prefixes for Host & Port properties</span></span><br><span class="line"><span class="comment"> * listed in order of priority.</span></span><br><span class="line"><span class="comment"> * Example:</span></span><br><span class="line"><span class="comment"> * {"ftp", "ftp.proxy", "ftpProxy", "proxy", "socksProxy"},</span></span><br><span class="line"><span class="comment"> * means for FTP we try in that oder:</span></span><br><span class="line"><span class="comment"> * + ftp.proxyHost & ftp.proxyPort</span></span><br><span class="line"><span class="comment"> * + ftpProxyHost & ftpProxyPort</span></span><br><span class="line"><span class="comment"> * + proxyHost & proxyPort</span></span><br><span class="line"><span class="comment"> * + socksProxyHost & socksProxyPort</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Note that the socksProxy should *always* be the last on the list</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> String[][] props = {</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * protocol, Property prefix 1, Property prefix 2, ...</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> {<span class="string">"http"</span>, <span class="string">"http.proxy"</span>, <span class="string">"proxy"</span>, <span class="string">"socksProxy"</span>},</span><br><span class="line"> {<span class="string">"https"</span>, <span class="string">"https.proxy"</span>, <span class="string">"proxy"</span>, <span class="string">"socksProxy"</span>},</span><br><span class="line"> {<span class="string">"ftp"</span>, <span class="string">"ftp.proxy"</span>, <span class="string">"ftpProxy"</span>, <span class="string">"proxy"</span>, <span class="string">"socksProxy"</span>},</span><br><span class="line"> {<span class="string">"socket"</span>, <span class="string">"socksProxy"</span>}</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>props这个二维数组的每一维第一个元素是协议类型,剩下的元素是<code>Host</code>和<code>Port</code>这两个属性的前缀。比如数组的第一维对应Http协议,前缀有<code>http.proxy</code>, <code>proxy</code>, <code>socksProxy</code>这三个,那么就可以组成3对有效的代理属性。</p><table><thead><tr><th>代理主机</th><th>代理端口</th></tr></thead><tbody><tr><td>http.proxy<code>Host</code></td><td>http.proxy<code>Port</code></td></tr><tr><td>proxy<code>Host</code></td><td>proxy<code>Port</code></td></tr><tr><td>socksProxy<code>Host</code></td><td>socksProxy<code>Port</code></td></tr></tbody></table><p>因此可以看到对于Http协议和Https协议<code>proxyHost</code>和<code>proxyPort</code>属性都生效。</p></blockquote></li><li><p>想要抓取Https数据需要导入Fiddler的证书</p><p>在JDK的<code>lib\security</code>目录下有一个<code>cacerts</code>文件,是受信任的证书颁发机构(CA)的证书集合,我们要做的就是把Fiddler的根证书加入到<code>cacerts</code>文件。</p><ol><li><p>备份<code>cacerts</code>文件,拷贝一份并重命名为<code>cacerts.bak</code></p></li><li><p>导出Fiddler的根证书<code>FiddlerRoot.cer</code>并和<code>cacerts</code>文件放在一起</p></li><li><p>执行以下命令</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">keytool -import -keystore ./cacerts -trustcacerts -file .\FiddlerRoot.cer -storepass changeit</span><br></pre></td></tr></table></figure></li></ol></li><li><p>效果</p><p><img src="https://cdn.jsdelivr.net/gh/codexvn/Images@master/2021/%E4%BD%BF%E7%94%A8Fiddler%E5%AF%B9Java%E7%A8%8B%E5%BA%8F%E8%BF%9B%E8%A1%8C%E6%8A%93%E5%8C%85/20211008181022-83cbe154f689d8259565fcf269673732.png"></p></li></ol>]]></content>
<tags>
<tag> SSL </tag>
<tag> Java </tag>
<tag> Fiddler </tag>
</tags>
</entry>
<entry>
<title>泰拉瑞亚实现自动钓鱼</title>
<link href="/2021/%E6%B3%B0%E6%8B%89%E7%91%9E%E4%BA%9A%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E9%92%93%E9%B1%BC.html"/>
<url>/2021/%E6%B3%B0%E6%8B%89%E7%91%9E%E4%BA%9A%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E9%92%93%E9%B1%BC.html</url>
<content type="html"><![CDATA[<blockquote><p>参考知乎上的一篇文章:<a href="https://zhuanlan.zhihu.com/p/367243635">https://zhuanlan.zhihu.com/p/367243635</a></p></blockquote><p>知乎上的做法是:</p><ol><li>修改游戏本身的<code>FishingCheck</code>函数,添加一个钓鱼的标志位<code>fishFlag</code>,初始为false,如果钓到了鱼就将<code>fishFlag</code>置为true</li><li>创建一个线程,内部是一个死循环,循环不断检测<code>fishFlag</code>,如果为true则执行2次鼠标单击事件,一次为收杆,一次为重新抛竿,然后将<code>fishFlag</code>重新设置为false</li></ol><p>这个做法有些问题,一个是游戏无法完全退出,退出后会残留一个线程;还有就是2次鼠标事件的间隔太短,有时游戏内收杆的动画还没结束,抛竿的鼠标事件就执行了</p><p>我参考文章的做法做了一些修改:</p><ol><li><p>下载dnSpy,如果要调试则需要下载32位版本</p><p><a href="https://github.com/dnSpyEx/dnSpy/releases">https://github.com/dnSpyEx/dnSpy/releases</a></p></li><li><p>dnSpy打开泰拉瑞亚主程序,找到<code>Projectile</code>类</p><img src="/2021/泰拉瑞亚实现自动钓鱼.html" style="zoom:50%;" /></li><li><p>右键<code>Projectile</code>类创建一个<code>DoFish</code>方法,并编辑函数</p><figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">extern</span> <span class="keyword">void</span> <span class="title">DoFish</span>()</span>; </span><br><span class="line"></span><br><span class="line"><span class="comment">//替换这一句为</span></span><br><span class="line"></span><br><span class="line">[<span class="meta">System.Runtime.InteropServices.DllImport(<span class="string">"user32"</span>)</span>]</span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">void</span> <span class="title">mouse_event</span>(<span class="params"><span class="built_in">int</span> dwFlags, <span class="built_in">int</span> dx, <span class="built_in">int</span> dy, <span class="built_in">int</span> dwData, <span class="built_in">int</span> dwExtraInfo</span>)</span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">DoFish</span>()</span>{</span><br><span class="line"> <span class="comment">//收杆</span></span><br><span class="line"> Projectile.mouse_event(<span class="number">2</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line"> Thread.Sleep(<span class="number">100</span>);</span><br><span class="line"> Projectile.mouse_event(<span class="number">4</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line"> Thread.Sleep(<span class="number">500</span>);<span class="comment">//这个是收杆和抛竿的间隔,如果人物站的比较远可以适当调高</span></span><br><span class="line"> <span class="comment">//下杆</span></span><br><span class="line"> Projectile.mouse_event(<span class="number">2</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line"> Thread.Sleep(<span class="number">100</span>);</span><br><span class="line"> Projectile.mouse_event(<span class="number">4</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line"> Thread.Sleep(<span class="number">500</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后添加引用<code>using System.Threading;</code></p></li><li><p>编辑<code>Projectile</code>类下的<code>FishingCheck</code>函数,末尾添加</p><figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> (StreamReader streamReader = <span class="keyword">new</span> StreamReader(<span class="string">"fish.conf"</span>))</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">if</span> (flag && streamReader.ReadLine() == <span class="string">"true"</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">new</span> Thread(<span class="keyword">new</span> ThreadStart(<span class="keyword">this</span>.DoFish)).Start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后添加引用<code>using System.Threading;</code>和<code>using System.IO;</code></p><p>在游戏目录下创建<code>fish.conf</code>,如果内容为true则开启自动钓鱼</p></li><li><p>保存为模块,然后就能愉快的钓鱼了</p></li></ol>]]></content>
<tags>
<tag> 游戏 </tag>
<tag> 泰拉瑞亚 </tag>
</tags>
</entry>
<entry>
<title>在Windows上编译zeal</title>
<link href="/2021/%E5%9C%A8Windows%E4%B8%8A%E7%BC%96%E8%AF%91zeal.html"/>
<url>/2021/%E5%9C%A8Windows%E4%B8%8A%E7%BC%96%E8%AF%91zeal.html</url>
<content type="html"><![CDATA[<p>经常使用zeal来查文档,但是在Windows上zeal经常闪退,而Arch上没有这个问题,就想着自己clone一份最新的代码自己编译。</p><h4 id="编译前的准备"><a href="#编译前的准备" class="headerlink" title="编译前的准备"></a>编译前的准备</h4><ul><li><p>使用<code>Visual Studio 2019</code>安装MSVC.</p></li><li><p>安装<a href="https://cmake.org/">CMake</a>.</p></li><li><p>下载Qt5.15.2.</p><p>这里推荐使用<a href="https://github.com/miurahr/aqtinstall">aqtinstall</a>,假设我们要把Qt下载到<code>D:\Qt</code></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#安装aqtinstall</span></span><br><span class="line">pip install aqtinstall</span><br><span class="line"><span class="comment">#安装qt到D:\Qt</span></span><br><span class="line">py -m aqt install --outputdir D:\Qt <span class="number">5.15</span><span class="number">.2</span> windows desktop win64_msvc2019_64 -m <span class="built_in">all</span> --base https://mirrors.tuna.tsinghua.edu.cn/qt</span><br><span class="line"><span class="comment">#安装OpenSSL到D:\Qt</span></span><br><span class="line">py -m aqt tool windows tools_openssl_x64 <span class="number">1.1</span><span class="number">.1</span>-<span class="number">10</span> qt.tools.openssl.win_x64 --outputdir D:\Qt --base https://mirrors.tuna.tsinghua.edu.cn/qt</span><br></pre></td></tr></table></figure><blockquote><p>Q: OpenSSL部分的为什么是<code>1.1.1-10</code>?</p><p>A: 可以从<a href="https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/tools_openssl_x64/qt.tools.openssl.win_x64/">这个链接</a>得出,可以看到在<strong>本文写下的时</strong>文件都是由<code>1.1.1-10</code>开头</p><p><img src="https://cdn.jsdelivr.net/gh/codexvn/Images@master/2021/%E5%9C%A8Windows%E4%B8%8A%E7%BC%96%E8%AF%91zeal/20210722161331-c24846b09537ad4c39ef3a90499bd3ed.png"></p></blockquote></li><li><p>下载<a href="https://libarchive.org/">libarchive</a>.</p><p>直链:<a href="https://libarchive.org/downloads/libarchive-v3.5.1-win64.zip">https://libarchive.org/downloads/libarchive-v3.5.1-win64.zip</a></p><p>解压在<code>D:\tmp\libarchive</code></p></li><li><p>下载<a href="https://sqlite.org/">SQLite</a>.</p><p>直链:<a href="https://sqlite.org/2021/sqlite-dll-win64-x64-3360000.zip">https://sqlite.org/2021/sqlite-dll-win64-x64-3360000.zip</a></p><p>解压之后放在<code>D:\tmp\sqlite3</code>,然后打开<code>Developer PowerShell for VS 2019</code>,执行以下命令:</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> D:\tmp\sqlite3</span><br><span class="line">LIB /DEF:sqlite3.def /MACHINE:X64</span><br></pre></td></tr></table></figure><p>会得到一个<code>sqlite3.lib</code></p></li><li><p>下载zeal的源码.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git clone -q --branch=master https://github.com/zealdocs/zeal.git D:/tmp/zeal</span><br></pre></td></tr></table></figure></li></ul><h4 id="开始编译"><a href="#开始编译" class="headerlink" title="开始编译"></a>开始编译</h4><ol><li><p>打开<code>Developer PowerShell for VS 2019</code>并将工具添加到环境变量中</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#Qt</span></span><br><span class="line"><span class="variable">$env:Path</span> = <span class="string">"D:\Qt\5.15.2\msvc2019_64\bin;<span class="variable">$env:Path</span>"</span></span><br><span class="line"><span class="comment">#libarchive</span></span><br><span class="line"><span class="variable">$env:Path</span> = <span class="string">"D:\tmp\libarchive\bin;<span class="variable">$env:Path</span>"</span></span><br><span class="line"><span class="comment">#sqlite3</span></span><br><span class="line"><span class="variable">$env:Path</span> = <span class="string">"D:\tmp\sqlite3;<span class="variable">$env:Path</span>"</span></span><br></pre></td></tr></table></figure></li><li><p>cd进入zeal文件夹执行以下命令</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cmake -B build</span><br><span class="line">cmake --build build --config release -j <span class="number">8</span></span><br></pre></td></tr></table></figure></li><li><p>在执行build命令的时候可能会出现以下错误:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">D:\tmp\zeal\src\libs\ui\browsertab.cpp(1,1): warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失 [D:\tmp\zeal\build\src\libs\ui\Ui.vcxproj]</span><br><span class="line">D:\tmp\zeal\src\libs\ui\browsertab.cpp(80,27): error C2001: 常量中有换行符 [D:\tmp\zeal\build\src\libs\ui\Ui.vcxproj]</span><br><span class="line">D:\tmp\zeal\src\libs\ui\browsertab.cpp(102,1): error C2001: 常量中有换行符 [D:\tmp\zeal\build\src\libs\ui\Ui.vcxproj]</span><br><span class="line">D:\tmp\zeal\src\libs\ui\browsertab.cpp(80,1): fatal error C1057: 宏扩展中遇到意外的文件结束 [D:\tmp\zeal\build\src\libs\ui\Ui.vcxproj]</span><br></pre></td></tr></table></figure><p>只需要打开<code>D:\tmp\zeal\src\libs\ui\browsertab.cpp</code>文件,将编码改为<code>UTF-8 with BOM</code>然后重新执行以下命令即可</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cmake --build build --config release -j <span class="number">8</span></span><br></pre></td></tr></table></figure></li><li><p>编译完成之后需要使用<code>windeployqt</code>帮助我们打包</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> D:\tmp\zeal\build\bin\Release\</span><br><span class="line">windeployqt .\zeal.exe</span><br><span class="line"><span class="built_in">cp</span> D:\tmp\libarchive\bin\archive.dll ./</span><br><span class="line"><span class="built_in">cp</span> D:\tmp\sqlite3\sqlite3.dll ./</span><br><span class="line"><span class="built_in">cp</span> D:\Qt\Tools\OpenSSL\Win_x64\bin\*.dll ./</span><br></pre></td></tr></table></figure></li></ol><h4 id="成品展示"><a href="#成品展示" class="headerlink" title="成品展示"></a>成品展示</h4><p><img src="https://cdn.jsdelivr.net/gh/codexvn/Images@master/2021/%E5%9C%A8Windows%E4%B8%8A%E7%BC%96%E8%AF%91zeal/20210722161322-ac64419ae898209181b385ccc70b406f.png"></p>]]></content>
<tags>
<tag> zeal </tag>
</tags>
</entry>
<entry>
<title>HTTPS知识点总结</title>
<link href="/2021/HTTPS%E7%9F%A5%E8%AF%86%E7%82%B9%E6%80%BB%E7%BB%93.html"/>
<url>/2021/HTTPS%E7%9F%A5%E8%AF%86%E7%82%B9%E6%80%BB%E7%BB%93.html</url>
<content type="html"><![CDATA[<blockquote><p>熬夜把<code>深入浅出HTTPS:从原理到实战</code>这本书看完了,总结一些主要的知识点,另外劝告各位<strong>熬夜伤身体</strong></p></blockquote><h3 id="TLS-SSL协议综述"><a href="#TLS-SSL协议综述" class="headerlink" title="TLS/SSL协议综述"></a>TLS/SSL协议综述</h3><h4 id="什么是TLS-SSL协议"><a href="#什么是TLS-SSL协议" class="headerlink" title="什么是TLS/SSL协议"></a>什么是TLS/SSL协议</h4><p>在早期,为了解决HTTP的安全问题,网景公司在1994年创建了SSL协议。一开始SSL协议只是浏览器的一个拓展,后来逐渐发展成一个独立协议,用于保证网络通信的认证和安全,SSL协议有SSLv1、SSLv2、SSLv3。</p><p>1996年,IETF(Internet Engineering Task Force)组织在SSLv3的基础上进一步标准化了该协议,微软为这个新协议取名TLS v1.0,目前最新的版本是TLSv1.3</p><h4 id="TLS-SSL协议的目标"><a href="#TLS-SSL协议的目标" class="headerlink" title="TLS/SSL协议的目标"></a>TLS/SSL协议的目标</h4><p>TLS/SSL协议在网络模型中位于<code>应用层</code>和<code>传输层</code>之间,构建在TCP上,构建在UDP上的称为<code>DTLS</code>。</p><img src="/2021/HTTPS知识点总结.html" alt="img" style="zoom: 33%;" /><p>TLS/SSL协议有4个目标:</p><ol><li><p>加密安全</p><p>通信双方的数据是安全的,不可篡改和伪造。</p></li><li><p>互操作性</p><p>TLS/SSL协议是标准的,任何开发者基于TLS/SSL RFC设计规范都可以实现该协议,开发者也很容易在应用中引入TLS/SSL协议。</p></li><li><p>可扩展性</p><p>密码学算法在不停迭代,随着时间推移会出现更安全的算法,为了保证安全TLS/SSL协议允许动态引入新的算法。在不同的通信环境中TLS/SSL协议允许通信算法协商出一个双方都支持的加密算法,这保证TLS/SSL协议能够工作在各种环境下。</p></li><li><p>效率</p><p>由于TLS/SSL协议涉及了很多密码学算法的运算,因此增加了通信延时和机器负载,但随着TLS/SSL协议的发展,一些新的技术和解决方案正在逐步提升TSL/SSL协议的通信效率</p></li></ol><h4 id="OpenSSL和TLS-SSL的关系"><a href="#OpenSSL和TLS-SSL的关系" class="headerlink" title="OpenSSL和TLS/SSL的关系"></a>OpenSSL和TLS/SSL的关系</h4><p>TSL/SSL协议的实现有很多种,比如OpenSSL、LibreSSL、BoringSSL,目前最通用的实现是OpenSSL。</p><img src="/2021/HTTPS知识点总结.html" alt="TLS/SSL协议的一些实现" style="zoom: 50%;" /><p>OpenSSL是一个底层密码库,封装了所有的密码学算法、证书管理、TLS/SSL协议实现,集成在大部分操作系统中,可以在代码中调用。而如果只是管理TLS/SSL证书则只需要使用OpenSSL命令行工具。</p><h4 id="HTTPS和TLS-SSL的关系"><a href="#HTTPS和TLS-SSL的关系" class="headerlink" title="HTTPS和TLS/SSL的关系"></a>HTTPS和TLS/SSL的关系</h4><p>TLS/SSL协议位于传输层和应用层之间,HTTP是应用层协议的一种,HTTP+TSL/SSL=HTTPS,应用只需要按照HTTP规范处理HTTP数据即可,对于开发者而言TLS/SSL带来的影响是透明的。</p><p>为了与HTTP区分开,HTTP默认使用80端口,而HTTPS默认使用443端口</p><h3 id="TLS-SSL协议中的算法"><a href="#TLS-SSL协议中的算法" class="headerlink" title="TLS/SSL协议中的算法"></a>TLS/SSL协议中的算法</h3><h4 id="TLS-SSL协议核心三大步骤:认证、密钥协商、数据加密"><a href="#TLS-SSL协议核心三大步骤:认证、密钥协商、数据加密" class="headerlink" title="TLS/SSL协议核心三大步骤:认证、密钥协商、数据加密"></a>TLS/SSL协议核心三大步骤:认证、密钥协商、数据加密</h4><ol><li><p>认证</p><p>认证通过<code>PKI技术</code>解决,PKI技术的核心是证书,证书通过密码学算法中的<code>数字签名技术</code>实现。</p><p>证书中包含服务器的身份信息和公钥,在进行<code>密钥协商</code>之前要先证书的认证,即保证通过证书获取到的公钥是服务器的公钥而不是攻击者的公钥。</p></li><li><p>密钥协商</p><p>通过<code>RSA算法</code>或者<code>DH算法</code>,客户端和服务器端会协商出一个<code>预备主密钥</code>,随后预备主密钥通过<code>密钥衍生算法</code>被转换成用于保证机密性和完整性需要的<code>主密钥</code>。</p><p>RSA算法和DH算法都属于<code>公开密钥算法</code>,习惯上称为<code>非对称加密算法</code>。</p></li><li><p>数据加密</p><p>为了保证数据的机密性、完整性和防篡改,需要使用加密算法和MAC算法,前者提供机密性,后者提供完整性和防篡改。</p><p>加密算法分为<code>对称加密算法</code>和<code>非对称加密算法</code>。相对来说,公开密钥算法尤其是RSA算法运算非常缓慢,因此在数据加密部分使用的是对称加密算法,使用<code>会话密钥</code>(即预备主密钥转换出的主密钥)作为加密密钥。</p></li></ol><h4 id="常用密码学算法"><a href="#常用密码学算法" class="headerlink" title="常用密码学算法"></a>常用密码学算法</h4><h5 id="加密基元"><a href="#加密基元" class="headerlink" title="加密基元"></a>加密基元</h5><ol><li><p>加密基元是一些功能单一、可靠的基础算法,通过加密基元构建出各种复杂的算法学算法供人类使用。</p></li><li><p><code>密码学伪随机数生成算法</code>和<code>密码学Hash算法</code>都是密码学中的基础算法,很多其他的密码学算法选择这两个算法作为加密基元。</p></li><li><p>密码学伪随机数生成算法:</p></li></ol><img src="/2021/HTTPS知识点总结.html" alt="密码学伪随机数生成算法" style="zoom:67%;" /><ol start="4"><li>密码学Hash算法:</li></ol><img src="/2021/HTTPS知识点总结.html" alt="密码学Hash算法" style="zoom: 67%;" /><h5 id="对称加密算法"><a href="#对称加密算法" class="headerlink" title="对称加密算法"></a>对称加密算法</h5><ol><li>对称加密算法就是通过一个算法和一个密钥对明文进行加密获得无意义的密文。</li></ol><img src="/2021/HTTPS知识点总结.html" alt="对称加密算法" style="zoom:50%;" /><ol start="2"><li><p>对称加密算法有两种类型,分别是<code>块密码算法</code>和<code>流密码算法</code>,推荐使用块密码算法。</p><ul><li>块密码算法:</li></ul><img src="/2021/HTTPS知识点总结.html" alt="块密码算法" style="zoom: 67%;" /><ul><li>块加密中的迭代模式:</li></ul><img src="/2021/HTTPS知识点总结.html" alt="块加密中的迭代模式" style="zoom:67%;" /><ul><li>流密码算法:</li></ul><img src="/2021/HTTPS知识点总结.html" alt="流密码算法" style="zoom:67%;" /></li><li><p>填充标准</p><p>对于块密码算法来说,明文长度必须是分组长度的倍数,如果不是倍数,必须有一种填充的机制,填充某些数据保证明文长度是分组长度的倍数,常见标准是<code>PKCS#7</code>和<code>PKCS#5</code>子标准中的填充标准。</p></li></ol><h5 id="消息验证码"><a href="#消息验证码" class="headerlink" title="消息验证码"></a>消息验证码</h5><ol><li><p>消息验证码用于提供数据的完整性,模型为: <code>MAC值=mac(消息,密钥)</code>。</p></li><li><p>MAC算法有2种形式,分别是CBC-MAC算法和HMAC算法。其中CBC-MAC算法从块密码算法的CBC分组模式演变而来,HMAC算法使用Hash算法作为加密基元,HMAC结合Hash算法有多种变种,比如HMAC-SHA-1、HMAC-SHA256、HMAC-SHA512。</p></li><li><p>对称加密算法和MAC算法一起使用以提供机密性和完整性和模式称为Authenticated Encryption(AE)加密模式,主要有三种:</p><ul><li>Encrypt-and-MAC (E&M)</li></ul><img src="/2021/HTTPS知识点总结.html" alt="Encrypt-and-MAC (E&M)" style="zoom: 50%;" /><ul><li>MAC-then-Encrypt (MtE)</li></ul><img src="/2021/HTTPS知识点总结.html" alt="MAC-then-Encrypt (MtE) " style="zoom: 50%;" /><ul><li>Encrypt-then-MAC (EtM)</li></ul><img src="/2021/HTTPS知识点总结.html" alt="Encrypt-then-MAC (EtM)" style="zoom: 50%;" /></li><li><p>AEAD加密模式是AE模式的一种变体,减轻了使用者的负担,主要有<code>CCM模式</code>、<code>GCM模式</code>、<code>ChaCha20-Poly1305</code>。</p></li><li><p>加密算法不能提供完整性</p><p>加密算法能够解决机密性的问题,但是不能保证数据是完整的。</p><p>攻击者截获加密数据之后,虽然没有密钥无法解密数据,但是依然可以修改数据,然后将修改后的数据发送给接收端。接收端收到数据之后如果成功解密,此时获取到的数据已经不是原来的数据,因此加密算法能够保证数据不泄露但是不能保证数据不被篡改。</p></li></ol><h5 id="公开密钥算法-非对称加密算法"><a href="#公开密钥算法-非对称加密算法" class="headerlink" title="公开密钥算法/非对称加密算法"></a>公开密钥算法/非对称加密算法</h5><ol><li><p>非对称加密算法分为功能很多,能够进行加密解密、密钥协商、数字签名。</p></li><li><p>与对称加密算法不同,非对称加密算法的密钥是一对,分为公钥和私钥。公钥和私钥功能上是一样的,一个密钥加密能够使用另一个密钥解密,但是生成密钥的侧重点不同。</p></li></ol><img src="/2021/HTTPS知识点总结.html" style="zoom: 50%;" /><ol start="3"><li><p>PKCS标准</p><p>公开密钥算法的标准称为PKCS(Public KeyCryptography Standards),这个标准由很多的子标准组成,指导使用者正确地使用公开密钥算法。PKCS标准最早是由RSA公司制定的,目前逐步交由标准化组织IETF(Internet Engineering TaskForce)的PKIX工作组来维护。前文中已经提到过<code>PKCS#7</code>和<code>PKCS#5</code>子标准中的填充标准。</p><img src="/2021/HTTPS知识点总结.html" alt="PKCS标准" style="zoom:67%;" /></li></ol><h5 id="密钥协商算法"><a href="#密钥协商算法" class="headerlink" title="密钥协商算法"></a>密钥协商算法</h5><p>协商算法分为RSA算法和DH算法,大概的流程如下:</p><ol><li><p>RSA</p><ul><li>客户端向服务器端发起连接请求,服务器端发送RSA密钥对的公钥给客户端。</li><li>客户端通过随机数生成器生成一个预备主密钥,用服务器的公钥加密并发送给服务器端。</li><li>服务器解密预备主密钥,假如能够正确解密,则说明客户端和服务器端共同协商出一个预备主密钥。</li></ul></li><li><p>DH算法</p><ul><li><p>客户端向服务器端发起连接请求。</p></li><li><p>服务器端生成一个RSA密钥对,并将公钥发送给客户端。</p></li><li><p>服务器端生成DH参数和服务器DH密钥对,用RSA私钥签名DH参数和服务器DH公钥,最后将签名值、DH参数、服务器DH公钥发送给客户端。</p></li><li><p>客户端通过服务器RSA的公钥验证签名,获取到DH参数和服务器DH公钥。</p></li><li><p>客户端通过DH参数生成客户端的DH密钥对,并将客户端DH公钥发送给服务器端。</p></li><li><p>客户端通过客户端DH私钥和服务器端DH公钥计算出预备主密钥。</p></li><li><p>服务器端接收到客户端的DH公钥,结合服务器的DH私钥计算出预备主密钥。</p></li><li><p>最终客户端和服务器端计算出的预备主密钥能够保持一致。</p></li></ul></li></ol><img src="/2021/HTTPS知识点总结.html" alt="DH算法" style="zoom: 50%;" /><p>图中的Z就是生成的预备主密钥</p><ol start="3"><li><p>DH算法分为2种类型,<code>静态DH算法(DH算法)</code>和<code>临时DH算法(EDH算法)</code>。</p><ul><li>静态DH算法中p和g两个参数永远是固定的,而且服务器的公钥(Ys)也是固定的,虽然节省了CPU资源的消耗,但是一旦服务器对应的DH私钥泄露,就不能保证安全性。</li><li>临时DH算法每次初始化连接时DH密钥对会重新生成,即使密钥泄露也只影响此次通信。</li></ul></li></ol><h5 id="椭圆曲线密码学(ECC)"><a href="#椭圆曲线密码学(ECC)" class="headerlink" title="椭圆曲线密码学(ECC)"></a>椭圆曲线密码学(ECC)</h5><ol><li><p>ECC是新一代的公开密钥算法,主要的优点就是安全性,极短的密钥能够提供很大的安全性。</p></li><li><p>比如224比特的ECC密钥和2048比特的RSA密钥可以达到同样的安全水平,由于ECC密钥具有很短的长度,运算速度非常快。</p></li><li><p>ECC可以结合其他公开密钥算法一起使用,比如<code>ECC + DH密钥协商算法 = ECDH密钥协商算法</code>、<code>ECC + 数字签名DSA算法 = ECDSA数字签名算法</code>。</p></li><li><p>ECC模型中一个关键点称为<code>命名曲线</code>,命名曲线与性能和安全性直接相关,大部分开发者可以直接使用常见的命名曲线</p><p>命名曲线设计标准:</p></li></ol><img src="/2021/HTTPS知识点总结.html" alt="命名曲线设计标准" style="zoom: 50%;" /><p>常见命名曲线:</p><img src="/2021/HTTPS知识点总结.html" alt="常见命名曲线" style="zoom: 50%;" /><h5 id="数字签名"><a href="#数字签名" class="headerlink" title="数字签名"></a>数字签名</h5><ol><li><p>消息验证码中的MAC算法只能保证消息不被篡改,但是却无法知道消息的发送者是谁,即无法防抵赖。对于公开密钥加密算法而言,私钥加密过的数据只有公钥才能够解密,即私钥的拥有者不能否认数据是其发出的。</p></li><li><p>RSA属于非对称加密算法,虽然能够用于实现数字签名。</p></li><li><p>数字签名技术有一个标准<code>DSS</code>,其标准算法就是<code>DSA签名算法</code>,只能进行签名,不能进行加密解密。</p></li><li><p>DSA算法能结合ECC,称为ECDSA数字签名算法,ECDSA的安全和性能更有保障。</p></li><li><p>数字签名的流程</p><ul><li><p>签名生成流程:</p><img src="/2021/HTTPS知识点总结.html" alt="签名生成流程" style="zoom: 50%;" /></li><li><p>签名验证流程:</p></li></ul></li></ol><img src="/2021/HTTPS知识点总结.html" alt="签名验证流程" style="zoom:50%;" /><h3 id="HTTPS完整流程"><a href="#HTTPS完整流程" class="headerlink" title="HTTPS完整流程"></a>HTTPS完整流程</h3><h4 id="握手层和加密层"><a href="#握手层和加密层" class="headerlink" title="握手层和加密层"></a>握手层和加密层</h4><img src="/2021/HTTPS知识点总结.html" alt="握手层和加密层" style="zoom:50%;" /><p>对于一个HTTPS回话,会先进行TCP的3次握手建立TCP连接,随后交由握手层协商安全通信需要的信息,最后将信息传递给加密层。</p><ol><li><p>使用RSA密码套件</p><img src="/2021/HTTPS知识点总结.html" alt="RSA密码套件" style="zoom:50%;" /></li><li><p>DHE_RSA密码套件</p></li></ol><img src="/2021/HTTPS知识点总结.html" alt="DHE_RSA密码套件" style="zoom:50%;" /><p>可以看到协商密码套件的流程是固定的</p><h4 id="密码套件"><a href="#密码套件" class="headerlink" title="密码套件"></a>密码套件</h4><ol><li>密码套件的结构并没有统一的标准,密码套件决定了用于密钥协商的算法。</li></ol><img src="/2021/HTTPS知识点总结.html" alt=" 密码套件结构" style="zoom: 50%;" /><ol start="2"><li><p>为了防止在密钥协商过程中消息被篡改,加密层开始加密HTTP数据之前要先验证协商过程中的数据没有被篡改。</p><ul><li><p>客户端将发送和接收到的所有握手消息组合在一起,然后计算出摘要数据,握手层使用密钥块对摘要数据进行加密和完整性保护,然后发送给服务器。</p></li><li><p>服务器接收到验证消息后,使用加密块解密出摘要数据。</p></li><li><p>紧接着服务器自行计算发送和接收的所有握手消息,再计算出消息的摘要数据,如果摘要数据和解密出的摘要数据相同,代表客户端发送的消息没有被篡改。</p></li></ul></li></ol><h3 id="证书"><a href="#证书" class="headerlink" title="证书"></a>证书</h3><blockquote><p>PKI(Public Key Infrastructure,称为公钥基础设施)是一个集合体,由一系列的软件、硬件、组织、个体、法律、流程组成,主要目的就是向客户端提供服务器身份认证,认证的基础就是必须找到一个可信的第三方组织,认证的技术方案就是数字签名技术。第三方组织能够使用数字签名技术管理证书,包括创建证书、存储证书、更新证书、撤销证书。 </p><p>引用自《深入浅出 HTTPS:从原理到实战》</p></blockquote><p>PKI技术有很多标准,HTTPS中最常用的标准是X.509标准和ASN.1标准。</p><ul><li>X.509目前有3个版本,现在最常用的是X.509 V3版本。</li><li>ASN.1类似于JSON或者XML这样的数据结构,用来描述证书结构。</li><li>X.509标准定义了证书应该包含的内容,而为了让机器和人更好地理解和组织X.509标准,可以采用ASN.1标准来描述X.509标准(或者说证书),ASN.1类似于伪代码,是一种可理解的数据结构。</li></ul><h4 id="X-509标准的内容"><a href="#X-509标准的内容" class="headerlink" title="X.509标准的内容"></a>X.509标准的内容</h4><ul><li>证书的作用,第三方认证机构为服务器实体(end entity)签发证书,证书校验方可以使用证书对服务器实体的身份进行认证。</li><li>证书文件的结构,证书是一个文件,理解证书的结构、属性、值非常重要。</li><li>管理证书,服务器实体(end entity)向CA机构申请证书的流程,CA机构审核服务器实体身份的标准,签发证书的流程。</li><li>校验证书,通过严谨的步骤校验证书,或者说校验服务器实体(end entity)身份,涉及两部分内容,一部分是证书签名校验,涉及证书链的概念。另外一部分是校验服务器实体属性,比如证书包含的域名、证书有效期等。</li><li>证书的撤销问题,包括CRL和OCSP协议等概念。</li></ul><h4 id="PKI的组成"><a href="#PKI的组成" class="headerlink" title="PKI的组成"></a>PKI的组成</h4><img src="/2021/HTTPS知识点总结.html" alt="PKI的组成" style="zoom: 50%;" /><ol><li><p>服务器实体,即需要证书的实体,域名的拥有者,服务器需要创建一个<code>CSR</code>文件用于表明身份</p></li><li><p>CA机构,即证书签发机构,验证服务器实体身份之后签发证书</p></li><li><p>RA机构,注册机构,主要审核服务器实体的有效信息,通常CA机构中包含了RA机构</p></li><li><p>证书仓库,CA机构签发的证书都保存在证书仓库中,证书可能被吊销或者过期,CA机构吊销的证书可以通过CRL和OCSP服务查询到</p></li><li><p>证书校验方,即校验证书真实性的软件。为了校验证书,证书校验方必须充分信任第三方CA机构,集成各个CA机构的根证书。最常见的证书校验方是浏览器</p></li></ol><h5 id="证书主要结构"><a href="#证书主要结构" class="headerlink" title="证书主要结构"></a>证书主要结构</h5><ol><li><p>version</p><p>证书的版本号,目前有3个版本(v1, v2, v3)</p></li><li><p>serialNumber</p><p>证书编号,对于不同的CA机构,证书编号是无法预测的</p></li><li><p>signature</p><p>证书的签名值,用于验证证书是否被修改。为了让证书校验方能够校验签名,证书中还会包含计算签名使用的<code>签名算法</code>和<code>摘要算法</code></p></li><li><p>issuer</p><p>签发证书的CA机构</p></li><li><p>validity</p><p>证书的有效期,CA机构是赢利的组织,证书使用期限越长价格越高</p></li><li><p>subject</p><p>服务器实体的名称,即证书申请者的名称。这个属性中包含很多信息,早期证书校验方校验证书的时候是将URL中的域名和证书subject值中的CN(Common Name)比较,如果一致,代表证书校验成功。现在由于一张证书可能包含多个域名,所以不再使用CN来校验证书域名了,而使用SAN证书扩展进行域名校验。</p></li><li><p>subjectPublicKeyInfo</p><p>subjectPublicKeyInfo包含两部分信息,分别是公开密钥算法和公钥值</p></li><li><p>issuerUniqueID和subjectUniqueID</p><p>分别代表CA机构和服务器实体的唯一编号,目前已经被相应的证书扩展替代。</p></li><li><p>extension</p><p>扩展是X.509 V3版本引入的,主要是为了扩展证书的含义,在不改变X.509版本的情况下,可以相对方便地增加证书新属性,新添加的扩展是否生效取决于证书校验方。</p><p>extension字段中可以包含多个扩展,每个扩展都有一个critical属性,如果该属性值等于真(TRUE),证书校验方必须严格处理。</p></li></ol><h5 id="证书主要扩展"><a href="#证书主要扩展" class="headerlink" title="证书主要扩展"></a>证书主要扩展</h5><ol><li><p>使用者可选名称(Subject Directory Attributes, SAN扩展)</p><p>在早期的X.509证书中,每个证书包含一个域名,现在可以通过这个拓展在一个证书中包含多个域名。</p></li><li><p>CA密钥标识符(Authority Key Identifier)</p><p>CA密钥标识符表明了哪个CA机构签发了证书。</p></li><li><p>使用者密钥标识符(Subject Key Identifier)</p><p>某个中间证书签署了一个服务器实体证书,中间证书中的使用者密钥标识符就是服务器实体证书的CA密钥标识符。</p></li><li><p>基础约束(basic constraints)</p><p>该扩展表示证书是否能够签发证书。</p></li><li><p>密钥用法(Key Usage)</p><p>定义了证书的用途,证书的用途有身份验证、数字签名、签发证书等,如果基础约束扩展被设置为True,那么该扩展也必须设置。</p></li><li><p>密钥扩展用法(Extended Key Usage)</p><p>表示证书的具体用途,一般是为了约束服务器实体证书,</p></li><li><p>CRL分发点</p><p>证书校验方用于在校验服务器实体证书时校验该证书是否已被吊销</p></li><li><p>CA机构信息(Authority Information Access)</p><p>这个扩展包含了CA机构的一些其他信息,主要的<code>OCSP服务地址</code>和<code>CA Issuers</code></p><ul><li><p>OCSP服务地址也是用于校验证书是否已被吊销。</p><p>与CRL不同,CRL是一个吊销证书列表,校验方需要将整个列表下载下来,然后检查待校验证书是否在这个列表中,这要求校验方经常性更新CRL文件。</p><p>而OCSP服务地址能够根据待校验证书的信息直接返回证书的吊销情况,不需要本地维护吊销列表。</p></li><li><p>CA Issuers保存了中间证书地址,当服务器没有发送完整的证书链是校验方能够从这个地址下载中间证。这保证了当没有获取到完整证书链时校验方能够通过迭代下载自行补全整个证书链。</p></li></ul></li></ol><h5 id="CSR"><a href="#CSR" class="headerlink" title="CSR"></a>CSR</h5><p>服务器实体在申请证书之前需要先生成一个CSR文件交给CA机构</p><ol><li><p>CSR文件包括两部分内容:</p><ul><li><p>生成证书的必要信息,比如域名信息、公钥。</p><p> 此处的公钥即进行<code>密钥协商</code>时使用的公钥,在进行密钥协商时需要获取到服务器的公钥,SSL/TLS中将公钥存储在证书中,这样既能提供公钥,又能证明公钥的身份,防止了中间人攻击。</p></li><li><p>服务器实体的证明材料,比如域名拥有者所在地区、拥有者名称等。</p></li></ul></li><li><p>CSR文件的生成过程</p><ul><li>服务器实体创建一对非对称加密密钥对。</li><li>服务器生成<code>CertificationRequestInfo</code>结构体,其中包含域名和刚刚创建密钥对中的公钥。</li><li>使用密钥对中的私钥对<code>CertificationRequestInfo</code>结构体进行签名获得签名值。</li><li>组合<code>CertificationRequestInfo</code>结构体和签名值获得<code>CSR</code>文件,将其发送给CA机构。</li></ul></li></ol><h4 id="证书链"><a href="#证书链" class="headerlink" title="证书链"></a>证书链</h4><p>从证书链的角度看,证书分为以下三种:</p><ul><li>根证书,又称为自签名证书,处于证书链的最顶端,是CA机构用于签发证书的证书。</li><li>服务器实体证书,处于证书链的最低端,其中包括了服务器实体的信息,服务器实体公钥和域名。</li><li>中间证书 ,处于服务器实体证书和根证书之间,能够用于签发证书,在一条证书链的中间证书可以有多个。</li></ul><img src="/2021/HTTPS知识点总结.html" style="zoom:50%;" /><ol><li><p>每中证书中都包含由CA签发者通过私钥提供的此证书的签名。</p></li><li><p>证书校验方通过签名者证书中的公钥校验当前证书,若证书没有被修改且证书有效则继续向上迭代校验。</p></li><li><p>根证书是证书链的最顶端,它的签名值是自己签名的,证书校验方完全相信根证书。</p></li><li><p>如果整个证书链校验过程没有出现问题则这个服务器实体证书合法,否则证书不合法。</p></li></ol><h4 id="委派和交叉认证"><a href="#委派和交叉认证" class="headerlink" title="委派和交叉认证"></a>委派和交叉认证</h4><ol><li><p>委派认证</p><ul><li><p>很多企业和个人都会申请证书,如果全部交由根CA机构签发会有很大的压力,并且不同地区语言和政策有所不同,所以根CA机构通常会委派一个中间CA机构,为其签发一张中间CA证书来代替根CA机构签发证书。</p></li><li><p>证书校验方只包含根CA机构的根证书,而根证书的升级是非常麻烦的,而中间证书升级却很简单,只需要为其重新签发一张新的CA证书即可。</p></li></ul></li><li><p>交叉认证</p><p>当一个新的根CA机构要投入使用,新的根CA证书不可能立刻被嵌入到证书校验方的可信任证书列表,解决的方案就是让另外一个根CA机构为其签发一张二级CA证书,由这个二级CA证书签发服务器实体证书,此时这个新CA机构能够立即投入使用。</p></li></ol><h4 id="证书吊销"><a href="#证书吊销" class="headerlink" title="证书吊销"></a>证书吊销</h4><p>证书吊销有两种状态,分别是:</p><ul><li>永久吊销,表示某张证书永久性地被吊销。</li><li>临时吊销,由于某些原因,某张证书只是临时被吊销。</li></ul><p>证书校验方通过<code>CRL</code>(Certificate Revocation List,证书吊销列表)和<code>OCSP</code>(Online Certificate Status Protocol,在线证书状态协议)来检查证书吊销状态</p><p>OCSP和CRL的区别:</p><ol><li><p>使用CRL校验证书吊销状态的时候,需要下载完整的CRLs文件,然后再进行吊销状态检查。</p></li><li><p>使用OCSP获取证书状态则简单得多,OCSP请求方为了查询某张证书的吊销状态,向OCSP提供方发送一个查询请求,OCSP提供方根据查询条件,直接返回该证书的吊销状态。</p></li><li><p>标准OCSP存在的问题已经由OCSP封套技术解决,OCSP封套技术提升了OCSP的服务质量。</p></li></ol><h4 id="证书透明度"><a href="#证书透明度" class="headerlink" title="证书透明度"></a>证书透明度</h4><p>证书透明度(Certificate Transparency, CT),通过证书透明度机制,CA机构、服务器实体、客户端能够监控、审计证书的签发、使用,确保证书是被正确使用的。</p><img src="/2021/HTTPS知识点总结.html" style="zoom:50%;" /><h3 id="证书分类"><a href="#证书分类" class="headerlink" title="证书分类"></a>证书分类</h3><ol><li><p>按照验证模式分类</p><p>证书可以分为三种类型,分别是DV证书、OV证书、EV证书,审核难度逐渐增加</p><p>其中DV适合于个人网站,OV证书适合企业和政府机构。</p><p>EV证书申请难度是最高的,对于DV、OV证书,浏览器地址栏上会出现一个绿色小锁图标,而对于EV证书,浏览器地址栏上除了绿色小锁图标,还会出现服务器实体的名称。</p></li><li><p>根据域名进行分类</p><ol><li><p>单域名证书</p><p>即简单的一张证书包含一个域名,比如<code>www.example.com</code></p></li><li><p>泛域名(Wildcard Domain)证书</p><p>一张证书可以包含注册域的所有子域名,比如 <code>*.example.com</code>,这个证书能够用于<code>www1.example.com</code>、<code>www2.example.com</code>等。</p></li><li><p>SAN(Subject Alternative Names)证书</p><p>使用SAN扩展的证书,能够在一张证书中包含多个域名,也被称为多域名证书,相当于将多个单域名证书合并为一个SAN证书。</p></li><li><p>SAN范域名证书</p><p>相当于将多个泛域名证书合并。</p></li></ol></li></ol>]]></content>
<tags>
<tag> SSL </tag>
<tag> Https </tag>
</tags>
</entry>
<entry>
<title>在RestTemplate中使用HttpComponentsClientHttpRequest解析gzip编码</title>
<link href="/2021/%E5%9C%A8RestTemplate%E4%B8%AD%E4%BD%BF%E7%94%A8HttpComponentsClientHttpRequest%E8%A7%A3%E6%9E%90gzip%E7%BC%96%E7%A0%81.html"/>
<url>/2021/%E5%9C%A8RestTemplate%E4%B8%AD%E4%BD%BF%E7%94%A8HttpComponentsClientHttpRequest%E8%A7%A3%E6%9E%90gzip%E7%BC%96%E7%A0%81.html</url>
<content type="html"><![CDATA[<blockquote><p>Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens</p></blockquote><p>今天在使用RestTemplate调用接口时发现无法对Gzip的数据进行解压,然后找到了这篇文章: <a href="https://www.jianshu.com/p/7075faf03f91">让RestTemplate使用和解析gzip编码</a>。</p><h3 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h3><p>文章提到RestTemplate默认不使用Gzip,也不解析 Gzip编码结果,这是因为RestTemplate默认SimpleBufferingClientHttpRequest类。通过idea可以看到RestTemplate的继承关系</p><blockquote><img src="/2021/在RestTemplate中使用HttpComponentsClientHttpRequest解析gzip编码.html" style="zoom: 50%;" /></blockquote><p>在HttpAccessor类中,ClientHttpRequestFactory这个private属性的默认值为一个SimpleClientHttpRequestFactory对象</p><blockquote><p><img src="https://cdn.jsdelivr.net/gh/codexvn/Images@master/2021/%E5%9C%A8RestTemplate%E4%B8%AD%E4%BD%BF%E7%94%A8HttpComponentsClientHttpRequest%E8%A7%A3%E6%9E%90gzip%E7%BC%96%E7%A0%81/20210531183532-e6a13e92022744c7f946cdbddb70a4d4.png"></p></blockquote><p>RestTemplate在获取数据时getForObject、getForEntity、postForObject、postForEntity、exchange等方法最终都是调用execute方法</p><blockquote><p><img src="https://cdn.jsdelivr.net/gh/codexvn/Images@master/2021/%E5%9C%A8RestTemplate%E4%B8%AD%E4%BD%BF%E7%94%A8HttpComponentsClientHttpRequest%E8%A7%A3%E6%9E%90gzip%E7%BC%96%E7%A0%81/20210531183540-43e9333b9640727650dc5367491af7eb.png"></p></blockquote><p>而execute方法会调用doExecute方法,在doExecute方法中我们可以看到这一句</p><blockquote><p><img src="https://cdn.jsdelivr.net/gh/codexvn/Images@master/2021/%E5%9C%A8RestTemplate%E4%B8%AD%E4%BD%BF%E7%94%A8HttpComponentsClientHttpRequest%E8%A7%A3%E6%9E%90gzip%E7%BC%96%E7%A0%81/20210531183546-fb72c03b5541af88a44f73626c3fcc56.png"></p></blockquote><p>createRequest方法属于HttpAccessor类,这个方法会通过调用getRequestFactory方法获得requestFactory属性,然后调用requestFactory属性的createRequest方法创建一个实现了ClientHttpRequest接口的对象。</p><blockquote><p><img src="https://cdn.jsdelivr.net/gh/codexvn/Images@master/2021/%E5%9C%A8RestTemplate%E4%B8%AD%E4%BD%BF%E7%94%A8HttpComponentsClientHttpRequest%E8%A7%A3%E6%9E%90gzip%E7%BC%96%E7%A0%81/20210531183553-c2a8d7d6f28d2417429ef2a49022dc1e.png"></p></blockquote><p>通过RestTemplate类的空参构造函数得到的RestTemplate中requestFactory属性为SimpleClientHttpRequestFactory,因此创建一个SimpleBufferingClientHttpRequest对象,而这个对象是不满足我们自动解析Gzip的要求的,这就是出现问题的原因</p><h3 id="解决"><a href="#解决" class="headerlink" title="解决"></a>解决</h3><p>RestTemplate类提供了另外一个构造函数,指定一个实现了ClientHttpRequestFactory的对象就能够将其注入到HttpAccessor父类的requestFactory属性中</p><blockquote><p><img src="https://cdn.jsdelivr.net/gh/codexvn/Images@master/2021/%E5%9C%A8RestTemplate%E4%B8%AD%E4%BD%BF%E7%94%A8HttpComponentsClientHttpRequest%E8%A7%A3%E6%9E%90gzip%E7%BC%96%E7%A0%81/20210531183558-afb8b880868017be7e050ee2edba7133.png"></p></blockquote><p>ClientHttpRequestFactory的实现类关系如图:</p><blockquote> <img src="/2021/在RestTemplate中使用HttpComponentsClientHttpRequest解析gzip编码.html" style="zoom:50%;" /></blockquote><p>HttpComponentsClientHttpRequestFactory能够创建HttpComponentsClientHttpRequest对象,而这个对象能够满足我们的需求</p><blockquote> <img src="/2021/在RestTemplate中使用HttpComponentsClientHttpRequest解析gzip编码.html" style="zoom:50%;" /></blockquote><p>要使用这个需要在项目中添加额外的依赖,以gradle为例:</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">implementation <span class="string">'org.apache.httpcomponents:httpclient:4.5.13'</span></span><br></pre></td></tr></table></figure><p>添加完依赖之后只需要将HttpComponentsClientHttpRequestFactory注入到RestTemplate对象即可</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">contextLoads</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">String</span> <span class="variable">url</span> <span class="operator">=</span> <span class="string">"XXX"</span>;</span><br><span class="line"> <span class="type">HttpComponentsClientHttpRequestFactory</span> <span class="variable">clientHttpRequestFactory</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HttpComponentsClientHttpRequestFactory</span>();</span><br><span class="line"> <span class="type">RestTemplate</span> <span class="variable">restTemplate</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RestTemplate</span>(clientHttpRequestFactory);</span><br><span class="line"> ResponseEntity<Qweather> qweatherResponseEntity = restTemplate.getForEntity(url, Qweather.class);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>运行之后可以看到自动解析了Gzip的数据</p><blockquote><p><img src="https://cdn.jsdelivr.net/gh/codexvn/Images@master/2021/%E5%9C%A8RestTemplate%E4%B8%AD%E4%BD%BF%E7%94%A8HttpComponentsClientHttpRequest%E8%A7%A3%E6%9E%90gzip%E7%BC%96%E7%A0%81/20210531183617-fb2095c6790edd97c04096cea6bf2f5e.png"></p></blockquote>]]></content>
<tags>
<tag> RestTemplate </tag>
<tag> Gzip </tag>
<tag> SpringBoot </tag>
</tags>
</entry>
<entry>
<title>申请Let’s Encrypt的免费SSL证书并在SpringBoot中使用</title>
<link href="/2021/let%E2%80%99s-encrypt%E6%B3%9B%E5%9F%9F%E5%90%8D%E5%85%8D%E8%B4%B9SSL%E8%AF%81%E4%B9%A6.html"/>
<url>/2021/let%E2%80%99s-encrypt%E6%B3%9B%E5%9F%9F%E5%90%8D%E5%85%8D%E8%B4%B9SSL%E8%AF%81%E4%B9%A6.html</url>
<content type="html"><![CDATA[<p>最近有想法申请一个SSL证书,阿里每年可以免费申请20个单域名证书,虽然够用但不是泛域名的证书,遂盯上了<a href="https://letsencrypt.org/">Let’s Encrypt</a>。</p><h2 id="Arch在安装上安装acme-sh"><a href="#Arch在安装上安装acme-sh" class="headerlink" title="Arch在安装上安装acme.sh"></a>Arch在安装上安装<a href="https://github.com/acmesh-official/acme.sh">acme.sh</a></h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -S acme.sh</span><br></pre></td></tr></table></figure><h2 id="注册账号"><a href="#注册账号" class="headerlink" title="注册账号"></a>注册账号</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">acme.sh --register-account --email [email protected]</span><br></pre></td></tr></table></figure><blockquote><p>注册账号时可以指定邮箱,当有重要事项发生时,Let’s Encrypt会发送邮件进行通知</p><p>续期证书没有限制,需要注意的是如果证书的有效期大于30天续期操作没有任何作用,证书的有效期是90天,一旦证书的剩余有效期小于30天Let’s Encrypt会发送邮件提醒用户续期证书</p></blockquote><h2 id="方法一-手动申请证书"><a href="#方法一-手动申请证书" class="headerlink" title="方法一 : 手动申请证书"></a>方法一 : 手动申请证书</h2><p>申请证书的方式有多种,此处只介绍最方便的一种</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">acme.sh --issue -d '*.codexvn.top' --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please</span><br></pre></td></tr></table></figure><p>结果为:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">[2021年 03月 24日 星期三 17:39:39 CST] Using CA: https://acme-v02.api.letsencrypt.org/directory</span><br><span class="line">[2021年 03月 24日 星期三 17:39:39 CST] Create account key ok.</span><br><span class="line">[2021年 03月 24日 星期三 17:39:39 CST] Registering account: https://acme-v02.api.letsencrypt.org/directory</span><br><span class="line">[2021年 03月 24日 星期三 17:39:41 CST] Registered</span><br><span class="line">[2021年 03月 24日 星期三 17:39:41 CST] ACCOUNT_THUMBPRINT='g2vqhg1sA6z1LaBwGUjs4ao0nDxHwjF2Lw_EkGW6Bjc'</span><br><span class="line">[2021年 03月 24日 星期三 17:39:41 CST] Creating domain key</span><br><span class="line">[2021年 03月 24日 星期三 17:39:41 CST] The domain key is here: /home/xvn/.acme.sh/*.codexvn.top/*.codexvn.top.key</span><br><span class="line">[2021年 03月 24日 星期三 17:39:41 CST] Single domain='*.codexvn.top'</span><br><span class="line">[2021年 03月 24日 星期三 17:39:41 CST] Getting domain auth token for each domain</span><br><span class="line">[2021年 03月 24日 星期三 17:39:43 CST] Getting webroot for domain='*.codexvn.top'</span><br><span class="line">[2021年 03月 24日 星期三 17:39:43 CST] Add the following TXT record:</span><br><span class="line">[2021年 03月 24日 星期三 17:39:43 CST] Domain: '_acme-challenge.codexvn.top'</span><br><span class="line">[2021年 03月 24日 星期三 17:39:43 CST] TXT value: 'nxAV26sqYlkB_hJyA6tC6suu7iPcVLuLVq9-AmnWX6E'</span><br><span class="line">[2021年 03月 24日 星期三 17:39:43 CST] Please be aware that you prepend _acme-challenge. before your domain</span><br><span class="line">[2021年 03月 24日 星期三 17:39:43 CST] so the resulting subdomain will be: _acme-challenge.codexvn.top</span><br><span class="line">[2021年 03月 24日 星期三 17:39:43 CST] Please add the TXT records to the domains, and re-run with --renew.</span><br><span class="line">[2021年 03月 24日 星期三 17:39:43 CST] Please add '--debug' or '--log' to check more details.</span><br><span class="line">[2021年 03月 24日 星期三 17:39:43 CST] See: https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh</span><br></pre></td></tr></table></figure><p>找到输出中的<code>Domain</code>和<code>TXT value</code>,然后去阿里云的控制台的域名解析中手动添加TXT记录,用于判断你是否拥有域名使用权</p><p>注意!记录不是填整个<code>Domain</code></p><img src="/2021/let’s-encrypt泛域名免费SSL证书.html" /><h3 id="重新生成证书"><a href="#重新生成证书" class="headerlink" title="重新生成证书"></a>重新生成证书</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">acme.sh --renew -d '*.codexvn.top' --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please </span><br></pre></td></tr></table></figure><p>结果为:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">[2021年 03月 24日 星期三 17:42:55 CST] Renew: '*.codexvn.top'</span><br><span class="line">[2021年 03月 24日 星期三 17:42:57 CST] Using CA: https://acme-v02.api.letsencrypt.org/directory</span><br><span class="line">[2021年 03月 24日 星期三 17:42:57 CST] Single domain='*.codexvn.top'</span><br><span class="line">[2021年 03月 24日 星期三 17:42:57 CST] Getting domain auth token for each domain</span><br><span class="line">[2021年 03月 24日 星期三 17:42:57 CST] Verifying: *.codexvn.top</span><br><span class="line">[2021年 03月 24日 星期三 17:43:02 CST] Success</span><br><span class="line">[2021年 03月 24日 星期三 17:43:02 CST] Verify finished, start to sign.</span><br><span class="line">[2021年 03月 24日 星期三 17:43:02 CST] Lets finalize the order.</span><br><span class="line">[2021年 03月 24日 星期三 17:43:02 CST] Le_OrderFinalize='https://acme-v02.api.letsencrypt.org/acme/finalize/116783946/8637907807'</span><br><span class="line">[2021年 03月 24日 星期三 17:43:03 CST] Downloading cert.</span><br><span class="line">[2021年 03月 24日 星期三 17:43:03 CST] Le_LinkCert='https://acme-v02.api.letsencrypt.org/acme/cert/03ad7155ad624f6efa5d19139ddb795e2c6b'</span><br><span class="line">[2021年 03月 24日 星期三 17:43:04 CST] Cert success.</span><br><span class="line">-----BEGIN CERTIFICATE-----</span><br><span class="line">----- 省略 -----</span><br><span class="line">-----END CERTIFICATE-----</span><br><span class="line">[2021年 03月 24日 星期三 17:43:04 CST] Your cert is in /home/xvn/.acme.sh/*.codexvn.top/*.codexvn.top.cer </span><br><span class="line">[2021年 03月 24日 星期三 17:43:04 CST] Your cert key is in /home/xvn/.acme.sh/*.codexvn.top/*.codexvn.top.key </span><br><span class="line">[2021年 03月 24日 星期三 17:43:04 CST] The intermediate CA cert is in /home/xvn/.acme.sh/*.codexvn.top/ca.cer </span><br><span class="line">[2021年 03月 24日 星期三 17:43:04 CST] And the full chain certs is there: /home/xvn/.acme.sh/*.codexvn.top/fullchain.cer </span><br></pre></td></tr></table></figure><p>这样我们就申请到一个有效期为90天的泛域名ssl证书</p><h2 id="方法二-通过DNS-API自动申请证书"><a href="#方法二-通过DNS-API自动申请证书" class="headerlink" title="方法二 : 通过DNS API自动申请证书"></a>方法二 : 通过DNS API自动申请证书</h2><p>此处以阿里云为例,首先要做的就是获取阿里云的操作API的<code>AccessKey ID</code>和<code>AccessKey Secret</code></p><p>然后执行</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">export Ali_Key="AccessKey ID的值"</span><br><span class="line">export Ali_Secret="AccessKey Secret的值"</span><br><span class="line"></span><br><span class="line">acme.sh --issue --dns dns_ali -d "*.codexvn.top" --force </span><br></pre></td></tr></table></figure><blockquote><p>执行一次之后<code>Ali_Key</code>和<code>Ali_Secret</code>会被记录,后续不需要重新设置环境变量</p><p>如果无法正常执行可以通过代理试试</p></blockquote><p>结果为:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">[2021年 03月 24日 星期三 17:54:17 CST] Using CA: https://acme-v02.api.letsencrypt.org/directory</span><br><span class="line">[2021年 03月 24日 星期三 17:54:17 CST] Create account key ok.</span><br><span class="line">[2021年 03月 24日 星期三 17:54:17 CST] Registering account: https://acme-v02.api.letsencrypt.org/directory</span><br><span class="line">[2021年 03月 24日 星期三 17:54:19 CST] Registered</span><br><span class="line">[2021年 03月 24日 星期三 17:54:19 CST] ACCOUNT_THUMBPRINT='3URstzWeVJMTV4j_F_dR5rRe2hr4G27HbQtdfz4MWWo'</span><br><span class="line">[2021年 03月 24日 星期三 17:54:19 CST] Creating domain key</span><br><span class="line">[2021年 03月 24日 星期三 17:54:19 CST] The domain key is here: /home/xvn/.acme.sh/*.codexvn.top/*.codexvn.top.key</span><br><span class="line">[2021年 03月 24日 星期三 17:54:19 CST] Single domain='*.codexvn.top'</span><br><span class="line">[2021年 03月 24日 星期三 17:54:19 CST] Getting domain auth token for each domain</span><br><span class="line">[2021年 03月 24日 星期三 17:54:21 CST] Getting webroot for domain='*.codexvn.top'</span><br><span class="line">[2021年 03月 24日 星期三 17:54:21 CST] Adding txt value: SBh1PMW1N5XksebSeXfQfLLPP0BCn1NmhfgMi19AjWQ for domain: _acme-challenge.codexvn.top</span><br><span class="line">[2021年 03月 24日 星期三 17:54:23 CST] The txt record is added: Success.</span><br><span class="line">[2021年 03月 24日 星期三 17:54:23 CST] Let's check each DNS record now. Sleep 20 seconds first.</span><br><span class="line">[2021年 03月 24日 星期三 17:54:44 CST] Checking codexvn.top for _acme-challenge.codexvn.top</span><br><span class="line">[2021年 03月 24日 星期三 17:54:46 CST] Domain codexvn.top '_acme-challenge.codexvn.top' success.</span><br><span class="line">[2021年 03月 24日 星期三 17:54:46 CST] All success, let's return</span><br><span class="line">[2021年 03月 24日 星期三 17:54:46 CST] Verifying: *.codexvn.top</span><br><span class="line">[2021年 03月 24日 星期三 17:54:50 CST] Success</span><br><span class="line">[2021年 03月 24日 星期三 17:54:50 CST] Removing DNS records.</span><br><span class="line">[2021年 03月 24日 星期三 17:54:50 CST] Removing txt: SBh1PMW1N5XksebSeXfQfLLPP0BCn1NmhfgMi19AjWQ for domain: _acme-challenge.codexvn.top</span><br><span class="line">[2021年 03月 24日 星期三 17:54:52 CST] Removed: Success</span><br><span class="line">[2021年 03月 24日 星期三 17:54:52 CST] Verify finished, start to sign.</span><br><span class="line">[2021年 03月 24日 星期三 17:54:52 CST] Lets finalize the order.</span><br><span class="line">[2021年 03月 24日 星期三 17:54:52 CST] Le_OrderFinalize='https://acme-v02.api.letsencrypt.org/acme/finalize/116785152/8638103911'</span><br><span class="line">[2021年 03月 24日 星期三 17:54:54 CST] Downloading cert.</span><br><span class="line">[2021年 03月 24日 星期三 17:54:54 CST] Le_LinkCert='https://acme-v02.api.letsencrypt.org/acme/cert/041c7e59e2a58d7548806e4f7ca10e678a27'</span><br><span class="line">[2021年 03月 24日 星期三 17:54:54 CST] Cert success.</span><br><span class="line">-----BEGIN CERTIFICATE-----</span><br><span class="line">----- 省略 -----</span><br><span class="line">-----END CERTIFICATE-----</span><br><span class="line">[2021年 03月 24日 星期三 17:54:54 CST] Your cert is in /home/xvn/.acme.sh/*.codexvn.top/*.codexvn.top.cer </span><br><span class="line">[2021年 03月 24日 星期三 17:54:54 CST] Your cert key is in /home/xvn/.acme.sh/*.codexvn.top/*.codexvn.top.key </span><br><span class="line">[2021年 03月 24日 星期三 17:54:54 CST] The intermediate CA cert is in /home/xvn/.acme.sh/*.codexvn.top/ca.cer </span><br><span class="line">[2021年 03月 24日 星期三 17:54:54 CST] And the full chain certs is there: /home/xvn/.acme.sh/*.codexvn.top/fullchain.cer</span><br></pre></td></tr></table></figure><h2 id="在SpringBoot中使用证书"><a href="#在SpringBoot中使用证书" class="headerlink" title="在SpringBoot中使用证书"></a>在SpringBoot中使用证书</h2><p>PKCS#12格式的证书是微软发布的一种格式,文件后缀一般是.pkcs12、.pfx、.p12,PKCS#12定义了一种存档文件格式,能够将证书和密钥对打包成一个文件,方便进行管理。</p><ol><li>通过openssl导出证书为PKCS#12格式<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl pkcs12 -export -in .acme.sh/\*.codexvn.top/\*.codexvn.top.cer -inkey .acme.sh/\*.codexvn.top/\*.codexvn.top.key -certfile .acme.sh/\*.codexvn.top/ca.cer -out codexvn.top.pfx</span><br></pre></td></tr></table></figure></li></ol><blockquote><p> 设定密码之后就可以在当前目录得到<code>codexvn.top.pfx</code>证书文件</p><p> 如果需要别名可以添加 <code>-name</code> 参数指定别名</p></blockquote><ol start="2"><li>通过acme.sh导出证书为PKCS#12格式</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">acme.sh --toPkcs -d "*.codexvn.top"</span><br></pre></td></tr></table></figure><blockquote><p>这种方法更为简单,推荐使用这种</p></blockquote><p>配置application.properties</p><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server.port</span>=<span class="string">8443</span></span><br><span class="line"><span class="attr">server.ssl.key-store</span>=<span class="string">classpath:codexvn.top.pfx</span></span><br><span class="line"><span class="attr">server.ssl.key-store-password</span>=<span class="string">xxx</span></span><br><span class="line"><span class="attr">server.ssl.key-store-type</span>=<span class="string">PKCS12</span></span><br><span class="line"><span class="attr">server.ssl.enabled</span>=<span class="string">true</span></span><br></pre></td></tr></table></figure><p>下面是证书信息</p><img src="/2021/let’s-encrypt泛域名免费SSL证书.html" style="zoom:50%;" />]]></content>
<tags>
<tag> SSL </tag>
<tag> Https </tag>
</tags>
</entry>
</search>