-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
302 lines (156 loc) · 101 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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>使用Certbot给你的网站添加HTTPS,部署Let's Encrypt通配符证书</title>
<link href="/certificate/use-certbot-create-letsencrypt-free-certificate.html"/>
<url>/certificate/use-certbot-create-letsencrypt-free-certificate.html</url>
<content type="html"><![CDATA[<p><img src="/certificate/use-certbot-create-letsencrypt-free-certificate/certbot-logo-1A.svg" alt="certbot"></p><h2 id="SSL证书"><a href="#SSL证书" class="headerlink" title="SSL证书"></a>SSL证书</h2><p>以往要在网站上启用HTTPS,是需要向一些证书颁发机构(CA)购买证书的,而如今很多证书机构都提供了免费证书,比如StartSSL、Let’s Encrypt、Symantec等等,在国内的话,可以用过阿里云、腾讯云等去申请Symantec DV SSL证书。</p><p>但是这些免费证书的过期时间大多数只有一年,而且大多只有DV证书(单域名证书)。单域名证书,顾名思义,只保护一个域名,使用起来就很不方便,比如我有两个子域名<code>www.example.com</code>,<code>shop.example.com</code>,那么我需要申请两个证书,子域名越多,管理起来越麻烦。</p><h2 id="通配符证书"><a href="#通配符证书" class="headerlink" title="通配符证书"></a>通配符证书</h2><p>通配符证书可以保护同一主域名下同一级所有的子域名,不限个数,申请证书时,域名填写为 <code>*.example.com</code></p><p>比如某公司旗下有一个域名叫 <code>example.com</code>,但因为业务需要,解析了很多子域名,比如有<code>www.example.com;login.example.com;shop.example.com;bill.example.com</code></p><p>可能多达几十上百个这样的子域名,但为每一个域名都申请一张证书,将会是很昂贵的费用。所以该公司申请了一张支持<code>*.example.com</code>的通配符证书,保护了任何前缀的子域名。</p><a id="more"></a> <h2 id="Let’s-Encrypt"><a href="#Let’s-Encrypt" class="headerlink" title="Let’s Encrypt"></a>Let’s Encrypt</h2><p><a href="https://letsencrypt.org">Let’s Encrypt</a>是由非营利互联网安全研究组织(ISRG)支持的免费,自动化和开放的证书认证机构。他们搞了一个非常有创意的事情,设计了一个<a href="https://ietf-wg-acme.github.io/acme/">ACME协议</a>,该协议通常在我们的Web主机上运行,目前该协议的版本是v1。</p><p>那什么是ACME协议,传统的 CA 机构是人工受理证书申请、证书更新、证书撤销,完全是手动处理的。而 ACME 协议规范化了证书申请、更新、撤销等流程,只要一个客户端实现了该协议的功能,通过客户端就可以向 Let’s Encrypt 申请证书,也就是说 Let’s Encrypt CA 完全是自动化操作的。</p><h2 id="申请-Let’s-Encrypt-通配符证书"><a href="#申请-Let’s-Encrypt-通配符证书" class="headerlink" title="申请 Let’s Encrypt 通配符证书"></a>申请 Let’s Encrypt 通配符证书</h2><p>为了实现通配符证书,Let’s Encrypt 对 ACME 协议的实现进行了升级,只有 v2 协议才能支持通配符证书,相关的新闻和技术点见:</p><ul><li><a href="https://community.letsencrypt.org/t/acme-v2-and-wildcard-certificate-support-is-live/55579">ACME v2 and Wildcard Certificate Support is Live</a></li><li><a href="https://community.letsencrypt.org/t/acme-v2-production-environment-wildcards/55578">ACME v2 Production Environment & Wildcards</a></li></ul><p>Let’s Encrypt 建议大多数使用shell访问的人使用 Certbot ACME客户端。它可以自动执行证书颁发和安装,而不会停机,也为不想自动配置的人提供专家模式。它很容易使用,适用于许多操作系统,并且具有出色的文档。访问<a href="https://certbot.eff.org/">Certbot站点</a>以获取适用于您的操作系统和Web服务器的定制说明。这个网址列举了不同的ACME客户端:<a href="https://letsencrypt.org/docs/client-options/">client-options</a></p><p>唯一不好的地方在于,Let’s Encrypt 证书的持续时间只有3个月,所以证书快到期了需要我们renew一下。</p><h3 id="Certbot"><a href="#Certbot" class="headerlink" title="Certbot"></a>Certbot</h3><p>由于从<a href="https://certbot.eff.org/">Certbot站点</a>上获取的客户端,有些暂时不支持<code>v2</code>协议,所以我推荐用Certbot提供的<code>certbot-auto</code>脚本。<br><figure class="highlight plain"><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">wget https://dl.eff.org/certbot-auto</span><br><span class="line">chmod a+x certbot-auto</span><br></pre></td></tr></table></figure></p><p>或者直接去<a href="https://github.com/certbot/certbot">github.com</a>下载最新的代码<br><figure class="highlight plain"><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">git clone https://github.com/certbot/certbot</span><br><span class="line">cd certbot</span><br><span class="line">chmod a+x certbot-auto</span><br></pre></td></tr></table></figure></p><p>获取完整的命令行帮助,你可以输入:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./certbot-auto --help all</span><br></pre></td></tr></table></figure></p><p>客户在申请 Let’s Encrypt 证书的时候,需要校验域名的所有权,证明操作者有权利为该域名申请证书,目前支持三种验证方式:</p><ul><li>dns-01:给域名添加一个 DNS TXT 记录。</li><li>http-01:在域名对应的 Web 服务器下放置一个 HTTP well-known URL 资源文件。</li><li>tls-sni-01:在域名对应的 Web 服务器下放置一个 HTTPS well-known URL 资源文件。</li></ul><p>而申请通配符证书,只能使用 dns-01 的方式</p><h3 id="实践"><a href="#实践" class="headerlink" title="实践"></a>实践</h3><p>简单介绍下申请步骤(域名:<code>imyxiao.com</code>)</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./certbot-auto certonly -d *.imyxiao.com --manual --manual-public-ip-logging-ok --preferred-challenges dns-01 --agree-tos --server https://acme-v02.api.letsencrypt.org/directory</span><br></pre></td></tr></table></figure><p>参数说明:</p><ul><li>certonly,表示安装模式,Certbot 有安装模式和验证模式两种类型的插件。</li><li>–manual 表示手动安装插件,Certbot 有很多插件,不同的插件都可以申请证书,用户可以根据需要自行选择</li><li>-d 为那些主机申请证书,如果是通配符,输入<code>*.imyxiao.com</code>(可以替换为你自己的域名)</li><li>–preferred-challenges dns-01,使用 DNS 方式校验域名所有权</li><li>–server,Let’s Encrypt ACME v2 版本使用的服务器不同于 v1 版本,需要显示指定。</li><li>–manual-public-ip-logging-ok 自动允许公共IP记录</li><li>–agree-tos 同意ACME服务器的用户协议</li></ul><p>如果使用<code>zsh</code>,添加<code>-d *.imyxiao.com</code>可能会报错,请去除这个参数,交互时手动输入</p><p>输入命令后输出:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">Requesting to rerun ./certbot-auto with root privileges...</span><br><span class="line">Saving debug log to /var/log/letsencrypt/letsencrypt.log</span><br><span class="line">Plugins selected: Authenticator manual, Installer None</span><br><span class="line">Obtaining a new certificate</span><br><span class="line">Performing the following challenges:</span><br><span class="line">dns-01 challenge for imyxiao.com</span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------------------</span><br><span class="line">Please deploy a DNS TXT record under the name</span><br><span class="line">_acme-challenge.imyxiao.com with the following value:</span><br><span class="line"></span><br><span class="line">WE3b-3uNQra1erQ-lHUQQifuVvGWSQFAKjqa7F_NtcU</span><br><span class="line"></span><br><span class="line">Before continuing, verify the record is deployed.</span><br><span class="line">-------------------------------------------------------------------------------</span><br><span class="line">Press Enter to Continue</span><br></pre></td></tr></table></figure></p><p>要求配置 DNS TXT 记录,从而校验域名所有权,也就是判断证书申请者是否有域名的所有权。</p><p>上面输出要求给 <code>_acme-challenge.imyxiao.com</code> 配置一条 TXT 记录,在没有确认 TXT 记录生效之前不要回车执行。</p><p>添加完TXT记录后,输入下列命令,验证TXT记录是否生效:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">$ dig txt _acme-challenge.imyxiao.com @8.8.8.8</span><br><span class="line"></span><br><span class="line">; <<>> DiG 9.10.3-P4-Debian <<>> txt _acme-challenge.imyxiao.com @8.8.8.8</span><br><span class="line">;; global options: +cmd</span><br><span class="line">;; Got answer:</span><br><span class="line">;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1372</span><br><span class="line">;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1</span><br><span class="line"></span><br><span class="line">;; OPT PSEUDOSECTION:</span><br><span class="line">; EDNS: version: 0, flags:; udp: 512</span><br><span class="line">;; QUESTION SECTION:</span><br><span class="line">;_acme-challenge.imyxiao.com.INTXT</span><br><span class="line"></span><br><span class="line">;; ANSWER SECTION:</span><br><span class="line">_acme-challenge.imyxiao.com. 119 INTXT"WE3b-3uNQra1erQ-lHUQQifuVvGWSQFAKjqa7F_NtcU"</span><br><span class="line"></span><br><span class="line">;; Query time: 164 msec</span><br><span class="line">;; SERVER: 8.8.8.8#53(8.8.8.8)</span><br><span class="line">;; WHEN: Fri Mar 16 12:25:40 CST 2018</span><br><span class="line">;; MSG SIZE rcvd: 112</span><br></pre></td></tr></table></figure><p>确认生效后,回车执行,输出如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">IMPORTANT NOTES:</span><br><span class="line"> - Congratulations! Your certificate and chain have been saved at:</span><br><span class="line"> /etc/letsencrypt/live/imyxiao.com/fullchain.pem</span><br><span class="line"> Your key file has been saved at:</span><br><span class="line"> /etc/letsencrypt/live/imyxiao.com/privkey.pem</span><br><span class="line"> Your cert will expire on 2018-06-16. To obtain a new or tweaked</span><br><span class="line"> version of this certificate in the future, simply run certbot-auto</span><br><span class="line"> again. To non-interactively renew *all* of your certificates, run</span><br><span class="line"> "certbot-auto renew"</span><br><span class="line"> - If you like Certbot, please consider supporting our work by:</span><br><span class="line"></span><br><span class="line"> Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate</span><br><span class="line"> Donating to EFF: https://eff.org/donate-le</span><br></pre></td></tr></table></figure><p>证书申请成功,证书和密钥保存在下列目录</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ sudo tree /etc/letsencrypt/live/imyxiao.com</span><br><span class="line">/etc/letsencrypt/live/imyxiao.com</span><br><span class="line">├── cert.pem -> ../../archive/imyxiao.com/cert2.pem</span><br><span class="line">├── chain.pem -> ../../archive/imyxiao.com/chain2.pem</span><br><span class="line">├── fullchain.pem -> ../../archive/imyxiao.com/fullchain2.pem</span><br><span class="line">├── privkey.pem -> ../../archive/imyxiao.com/privkey2.pem</span><br><span class="line">└── README</span><br><span class="line"></span><br><span class="line">0 directories, 5 files</span><br></pre></td></tr></table></figure><p>这样证书就Ok了,由于证书持续时间只有三个月,所以需要在快到期的时候重新持行一下命令生成新的证书。或者:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./certbot-auto renew</span><br></pre></td></tr></table></figure><p>引用:</p><blockquote><ul><li><a href="https://www.jianshu.com/p/c5c9d071e395">Let’s Encrypt 终于支持通配符证书了</a></li></ul></blockquote>]]></content>
<categories>
<category> certificate </category>
</categories>
<tags>
<tag> certificate </tag>
<tag> certbot </tag>
<tag> letsencrypt </tag>
<tag> https </tag>
</tags>
</entry>
<entry>
<title>转换Java keytools的keystore证书到OPENSSL的PEM格式文件</title>
<link href="/java/keystore-convert-pem.html"/>
<url>/java/keystore-convert-pem.html</url>
<content type="html"><![CDATA[<p>使用https直接连接后端的tomcat,证书是通过JDK自带的keytool生成的.假如keytool生成密钥对<code>server.keystore</code>如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">keytool -genkey -<span class="built_in">alias</span> tomcat-server -keyalg RSA -keypass xxxxxxx -storepass xxxxxxx -keystore server.keystore</span><br></pre></td></tr></table></figure><p>现决定迁移到nginx做ssl,但是keytool生成的证书都是二进制data,nginx使用的是OPENSSL标准的PEM+key文件,即ascii文本格式的密钥,所以需要作证书转换.</p><p>首先将服务器证书导出为证书文件<code>server.cer</code>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">keytool -<span class="built_in">export</span> -<span class="built_in">alias</span> tomcat-server -storepass xxxxxxx -file server.cer -keystore server.keystore</span><br></pre></td></tr></table></figure><p>cer文件到PEM文件的转换较简单。这两者都是X509证书,编码不同,使用openssl工具即可:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl x509 -inform der -<span class="keyword">in</span> server.cer -out server.pem</span><br></pre></td></tr></table></figure><a id="more"></a> <p>至于keystore转换就比较麻烦,首先使用<code>http://download.csdn.net/detail/cwxzz/1072684</code>这里的工具,PFX格式证书和JAVA keyStore证书相互转换,先将keystore转换为PFX证书。修改java代码,填入keystore路径,生成文件的路径,KEYSTORE_PASSWORD。运行后得到PFX证书<code>server.pfx</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String PFX_KEYSTORE_FILE = <span class="string">"server.pfx"</span>;</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String KEYSTORE_PASSWORD = <span class="string">"xxxxxxx"</span>;</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String JKS_KEYSTORE_FILE = <span class="string">"server.keystore"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">coverToPfx</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> KeyStore inputKeyStore = KeyStore.getInstance(<span class="string">"JKS"</span>);</span><br><span class="line"> FileInputStream fis = <span class="keyword">new</span> FileInputStream(JKS_KEYSTORE_FILE);</span><br><span class="line"> <span class="keyword">char</span>[] nPassword;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (KEYSTORE_PASSWORD.trim().equals(<span class="string">""</span>)) {</span><br><span class="line"> nPassword = <span class="keyword">null</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> nPassword = KEYSTORE_PASSWORD.toCharArray();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> inputKeyStore.load(fis, nPassword);</span><br><span class="line"> fis.close();</span><br><span class="line"></span><br><span class="line"> KeyStore outputKeyStore = KeyStore.getInstance(<span class="string">"PKCS12"</span>);</span><br><span class="line"> outputKeyStore.load(<span class="keyword">null</span>, KEYSTORE_PASSWORD.toCharArray());</span><br><span class="line"> Enumeration enums = inputKeyStore.aliases();</span><br><span class="line"> <span class="keyword">while</span> (enums.hasMoreElements()) {</span><br><span class="line"> String keyAlias = (String) enums.nextElement();</span><br><span class="line"> System.out.println(<span class="string">"alias=["</span> + keyAlias + <span class="string">"]"</span>);</span><br><span class="line"> <span class="keyword">if</span> (inputKeyStore.isKeyEntry(keyAlias)) {</span><br><span class="line"> Key key = inputKeyStore.getKey(keyAlias, nPassword);</span><br><span class="line"> Certificate[] certChain = inputKeyStore.getCertificateChain(keyAlias);</span><br><span class="line"> outputKeyStore.setKeyEntry(keyAlias, key, KEYSTORE_PASSWORD.toCharArray(), certChain);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> FileOutputStream out = <span class="keyword">new</span> FileOutputStream(PFX_KEYSTORE_FILE);</span><br><span class="line"> outputKeyStore.store(out, nPassword);</span><br><span class="line"> out.close();</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>接下来使用openssl从PFX中提取私钥</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl pkcs12 -<span class="keyword">in</span> server.pfx -nocerts -nodes -out server.key</span><br></pre></td></tr></table></figure><p>这里也需要输入生成证书时使用的密码。这样ascii格式的key文件<code>server.key</code>也可以使用了。</p><p>如果私钥带有密码,则需要移除密码:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl rsa -<span class="keyword">in</span> server.key -out server.origin.key</span><br></pre></td></tr></table></figure><p>nginx配置ssl示例(ssl开头的属性与证书配置有直接关系):</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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">server {</span><br><span class="line"> listen 443;</span><br><span class="line"> server_name localhost;</span><br><span class="line"> ssl on;</span><br><span class="line"> root html;</span><br><span class="line"> index index.html index.htm;</span><br><span class="line"> ssl_certificate server.pem;</span><br><span class="line"> ssl_certificate_key server.key;</span><br><span class="line"> ssl_session_timeout 5m;</span><br><span class="line"> ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;</span><br><span class="line"> ssl_protocols TLSv1 TLSv1.1 TLSv1.2;</span><br><span class="line"> ssl_prefer_server_ciphers on;</span><br><span class="line"> location / {</span><br><span class="line"> root html;</span><br><span class="line"> index index.html index.htm;</span><br><span class="line"> }</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">keytool -list -rfc --keystore server.keystore | openssl x509 -inform pem -pubkey</span><br></pre></td></tr></table></figure><p>输入密码,即可输出公钥及证书</p><p>引用:</p><blockquote><ul><li><a href="https://www.cnblogs.com/interdrp/p/4880891.html">转换java keytools的keystore证书到OPENSSL的PEM格式文件</a></li><li><a href="http://download.csdn.net/download/cwxzz/1072684">PFX格式证书和JAVA keyStore证书相互转换</a></li></ul></blockquote>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> certificate </tag>
<tag> https </tag>
<tag> keystore </tag>
<tag> pem </tag>
<tag> nginx </tag>
</tags>
</entry>
<entry>
<title>SonarQube的安装与使用</title>
<link href="/docker/sonarqube.html"/>
<url>/docker/sonarqube.html</url>
<content type="html"><![CDATA[<p>Sonar是一个用于代码质量管理的开源平台,用于管理源代码的质量,帮助我们从源码中找出潜在的bug,未使用的代码,复杂的表达式,未覆盖的代码,糟糕的代码,重复的代码等等<br>通过插件形式,可以支持包括java,C#,C/C++,PL/SQL,Cobol,JavaScrip,Groovy等等二十几种编程语言的代码质量管理与检测</p><p>更多介绍请看官方网站<a href="https://www.sonarqube.org/">SonarQube</a>,官方文档:<a href="https://docs.sonarqube.org/display/SONAR/Documentation">SONAR Documentation</a>,</p><h2 id="SonarQube安装"><a href="#SonarQube安装" class="headerlink" title="SonarQube安装"></a>SonarQube安装</h2><p>推荐使用<code>docker</code>安装,官方<code>docker</code>地址:<a href="https://hub.docker.com/_/sonarqube/">SonarQube</a>, 简单的运行一个<code>SonarQube</code>服务:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --name sonarqube -p 9000:9000 -p 9092:9092 sonarqube</span><br></pre></td></tr></table></figure><p>打开<code>http://localhost:9000/</code>,默认帐号和密码都为<code>admin</code></p><h3 id="数据库配置"><a href="#数据库配置" class="headerlink" title="数据库配置"></a>数据库配置</h3><p>默认情况使用的一个内嵌的<code>H2</code>数据库,肯定不适合生产情境,数据库配置的三个变量</p><ul><li><code>SONARQUBE_JDBC_USERNAME</code></li><li><code>SONARQUBE_JDBC_PASSWORD</code></li><li><code>SONARQUBE_JDBC_URL</code></li></ul><p>官方示例(需要安装一个<code>postgresql</code>):<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --name sonarqube \</span><br><span class="line"> -p 9000:9000 -p 9092:9092 \</span><br><span class="line"> -e SONARQUBE_JDBC_USERNAME=sonar \</span><br><span class="line"> -e SONARQUBE_JDBC_PASSWORD=sonar \</span><br><span class="line"> -e SONARQUBE_JDBC_URL=jdbc:postgresql://localhost/sonar \</span><br><span class="line"> sonarqube</span><br></pre></td></tr></table></figure></p><p>由于我已经有一个<code>mysql</code>容器:<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run --name wlcx_mysql -p 3306:3306 -v /srv/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=password -d mysql</span><br></pre></td></tr></table></figure></p><p>所以使用链接容器的方式使用数据库,容器名为<code>wlcx_mysql</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --link wlcx_mysql:wlcx_mysql --name sonarqube \</span><br><span class="line"> -p 9000:9000 -p 9092:9092 \</span><br><span class="line"> -e SONARQUBE_JDBC_USERNAME=root \</span><br><span class="line"> -e SONARQUBE_JDBC_PASSWORD=password \</span><br><span class="line"> -e <span class="string">"SONARQUBE_JDBC_URL=jdbc:mysql://wlcx_mysql:3306/sonar?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false"</span> \</span><br><span class="line"> sonarqube</span><br></pre></td></tr></table></figure><p>也可以使用<code>docker-compose</code>的方式,更加的方便,请参考:<a href="https://github.com/SonarSource/docker-sonarqube/blob/master/recipes.md">docker sonarqube recipes</a></p><h2 id="SonarQube使用"><a href="#SonarQube使用" class="headerlink" title="SonarQube使用"></a>SonarQube使用</h2><p>使用默认的帐号登录之后,可以:</p><ul><li>生成一个代替帐号的<code>token</code></li><li>修改一个<code>admin</code>的密码</li><li>可以在<code>Administration</code>=><code>System</code>=><code>Update Center</code>,安装中文插件和其它要分析的语言的插件</li></ul><p>拿到了<code>token</code>后,介绍如何用<code>sonar</code>分析<code>Maven</code>与<code>Gradle</code>项目</p><h3 id="Maven项目"><a href="#Maven项目" class="headerlink" title="Maven项目"></a>Maven项目</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mvn clean verify sonar:sonar \</span><br><span class="line"> -Dsonar.host.url=http://localhost:9000 \</span><br><span class="line"> -Dsonar.login=token</span><br></pre></td></tr></table></figure><p>运行这个命令即可,为了固定<code>sonar</code>的mvn插件版本,可以配置:<br><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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">build</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">pluginManagement</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">plugins</span>></span></span><br><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.sonarsource.scanner.maven<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>sonar-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>3.3.0.603<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">plugins</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">pluginManagement</span>></span></span><br><span class="line"><span class="tag"></<span class="name">build</span>></span></span><br></pre></td></tr></table></figure></p><p>更多详细配置:<a href="https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner+for+Maven">Analyzing with SonarQube Scanner for Maven</a></p><h3 id="Gradle项目"><a href="#Gradle项目" class="headerlink" title="Gradle项目"></a>Gradle项目</h3><p>配置插件:<br><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">buildscript {</span><br><span class="line"> repositories {</span><br><span class="line"> maven {</span><br><span class="line"> url <span class="string">"https://plugins.gradle.org/m2/"</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> dependencies {</span><br><span class="line"> classpath <span class="string">"org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.5"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">apply <span class="string">plugin:</span> <span class="string">"org.sonarqube"</span></span><br></pre></td></tr></table></figure></p><p>对于<code>Gradle 2.1+</code> ,可以这样配置:<br><figure class="highlight plain"><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">plugins {</span><br><span class="line"> id "org.sonarqube" version "2.5"</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>运行命令:<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">gradle sonarqube \</span><br><span class="line"> -Dsonar.host.url=http://localhost:9000 \</span><br><span class="line"> -Dsonar.login=token</span><br></pre></td></tr></table></figure></p><p>或将<code>sonar</code>地址与<code>token</code>,配置到项目下的<code>gradle.properties</code>或<code>~/.gradle/gradle.properties</code>当中<br><figure class="highlight plain"><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">systemProp.sonar.host.url=http://localhost:9000</span><br><span class="line"></span><br><span class="line">systemProp.sonar.login=token</span><br></pre></td></tr></table></figure></p><p>然后执行<code>gradle sonarqube</code>即可,更多详细配置请看:<a href="https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner+for+Gradle">Analyzing with SonarQube Scanner for Gradle</a></p>]]></content>
<categories>
<category> docker </category>
</categories>
<tags>
<tag> sonarqube </tag>
<tag> sonar </tag>
<tag> docker </tag>
</tags>
</entry>
<entry>
<title>自动挂载硬盘</title>
<link href="/linux/auto-mount-disk.html"/>
<url>/linux/auto-mount-disk.html</url>
<content type="html"><![CDATA[<p>我的系统(deepin)装在了固态硬盘上,并且只分配了20G空间.并不是很够用,所以很多数据、程序放在了之前的机械硬盘上,但是硬盘不是自己挂载的,需要开机后点一下才会挂载,挺麻烦。所以需要开机自动挂载硬盘。</p><h3 id="查询NTFS磁盘的UUID"><a href="#查询NTFS磁盘的UUID" class="headerlink" title="查询NTFS磁盘的UUID"></a>查询NTFS磁盘的UUID</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">sudo blkid</span><br></pre></td></tr></table></figure><p>查询出所以磁盘的信息,其中一条如下:<br><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">/dev/sda5: LABEL=<span class="string">"work"</span> UUID=<span class="string">"0005F366000EF1F7"</span> TYPE=<span class="string">"ntfs"</span> PARTUUID=<span class="string">"e6f99ced-05"</span></span><br></pre></td></tr></table></figure></p><h3 id="编辑-etc-fstab"><a href="#编辑-etc-fstab" class="headerlink" title="编辑/etc/fstab"></a>编辑/etc/fstab</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">sudo vim /etc/fstab</span><br></pre></td></tr></table></figure><p>将下面这段添加到末尾即可,<code>UUID</code>为上一步查询所得,<code>/media/yxiao/work</code>是挂载点<br><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">UUID=<span class="number">0005F</span>366000EF1F7 /media/yxiao/work ntfs defaults <span class="number">0</span> <span class="number">0</span></span><br></pre></td></tr></table></figure></p><p>保存退出即可。下次开机会自动挂载。</p>]]></content>
<categories>
<category> linux </category>
</categories>
<tags>
<tag> mount </tag>
<tag> disk </tag>
<tag> deepin </tag>
</tags>
</entry>
<entry>
<title>Spring Boot静态资源处理</title>
<link href="/java/Spring-Boot-Static-Content.html"/>
<url>/java/Spring-Boot-Static-Content.html</url>
<content type="html"><![CDATA[<p>Spring Boot 静态资源处理,默认配置路径为 <code>classpath</code> 下的<code>/static</code> (或 <code>/public</code> 或 <code>/resources</code> 或 <code>/META-INF/resources</code>),即将<code>/**</code>映射到这几个路径下。Spring Boot 启动的时候,我们会看到如下日志:</p><figure class="highlight plain"><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">2016-12-16 22:27:56 INFO 9812 - [restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]</span><br><span class="line">2016-12-16 22:27:56 INFO 9812 - [restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]</span><br><span class="line">2016-12-16 22:27:56 INFO 9812 - [restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]</span><br></pre></td></tr></table></figure><p>可以看到,Spring Boot 静态资源处理使用 Spring MVC 的<code>ResourceHttpRequestHandler</code>来实现。这里除了将<code>/**</code>映射到默认资源路径下,还为我们配置一个<code>favicon.ico</code>以及后面会讲到的<code>webjars</code>。Spring Boot中可以通过添加一个配置类,继承<code>WebMvcConfigurerAdapter</code>来自定义Spring MVC的配置,其中<code>addResourceHandlers</code>方法可以用来自定义静态资源处理,比如修改静态资源的缓存时间。这里我们想要替换默认的静态资源列表:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">WebMvcConfig</span> <span class="keyword">extends</span> <span class="title">WebMvcConfigurerAdapter</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addResourceHandlers</span><span class="params">(ResourceHandlerRegistry registry)</span> </span>{</span><br><span class="line"> registry.addResourceHandler(<span class="string">"/**"</span>)</span><br><span class="line"> .addResourceLocations(<span class="string">"classpath:/mydir1/"</span>,<span class="string">"classpath:/mydir2/"</span>,<span class="string">"classpath:/mydir3/"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>此时,<code>/**</code>被映射到了<code>"classpath:/mydir1/"</code>,<code>"classpath:/mydir2/"</code>,<code>"classpath:/mydir3/"</code>下,要注意,如果替换了默认静态资源地址,那么默认欢迎页面<code>index.html</code>也将会被替换到我们自定义配置的任何路径下,Spring Boot相关的实现代码在<code>ResourceProperties</code>,这个类使用了<code>ConfigurationProperties</code>注解,前缀(prefix)为<code>spring.resources</code>,静态资源字段为<code>staticLocations</code>,那么我们也可以通过配置<code>spring.resources.staticLocations</code>这个properties来替换静态资源地址。</p><hr><a id="more"></a> <h2 id="1-使用webjars"><a href="#1-使用webjars" class="headerlink" title="1.使用webjars"></a>1.使用webjars</h2><blockquote><p>我们在Web开发中,前端页面中用了越来越多的JS或CSS,如jQuery等等,平时我们是将这些Web资源拷贝到Java的目录下,这种通过人工方式拷贝可能会产生版本误差,拷贝版本错误,前端页面就无法正确展示。 WebJars 就是为了解决这种问题衍生的,将这些Web前端资源打包成Java的Jar包,然后借助Maven这些依赖库的管理,保证这些Web资源版本唯一性,升级也比较容易。</p></blockquote><p>webjars中的资源所在路径为:<code>/META-INF/resources/webjars/</code>,其中<code>/META-INF/resources</code>是默认资源路径,从刚才的日志当中也可以看到,Spirng Boot为其配置一个<code>/webjars/**</code>的Url映射。我们可以去<a href="http://www.webjars.org/" title="http://www.webjars.org/">http://www.webjars.org/</a> 找到我们想要的资源,将其依赖加入我们项目当中,即可使用这个资源了。例如:</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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">groupId</span>></span>org.webjars<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">artifactId</span>></span>jquery<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">version</span>></span>2.1.4<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><p>这里dependency的version即为静态资源的版本号,模板中(thymeleaf)使用:<br><figure class="highlight html"><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="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">th:src</span>=<span class="string">"@{/webjars/jquery/2.1.4/jquery.js}"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br></pre></td></tr></table></figure></p><h2 id="2-静态资源处理"><a href="#2-静态资源处理" class="headerlink" title="2.静态资源处理"></a>2.静态资源处理</h2><p>Spring Boot 为我们提供了 Spring MVC 中比较高级的静态资源处理方法,比如为webjars添加动态版本号、清除缓存的静态资源等。</p><h3 id="2-1-为Webjars添加动态版本号"><a href="#2-1-为Webjars添加动态版本号" class="headerlink" title="2.1.为Webjars添加动态版本号"></a>2.1.为Webjars添加动态版本号</h3><p>实际开发当中,我们不可避免的需要升级第三方js、css库的版本,如果我们有100个页面,那么一个一个页面的去替换版本号显然不是个好方法,Spring Boot项目中我们只需要简单的添加<code>webjars-locator</code>依赖,即可为Webjars添加动态版本号管理。<br><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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.webjars<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>webjars-locator<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></p><p>此依赖使用<code>spring-boot-dependencies</code>中统一管理的版本号,然后模板中就不需要写版本号了:<br><figure class="highlight html"><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="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">th:src</span>=<span class="string">"@{/webjars/jquery/jquery.js}"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br></pre></td></tr></table></figure></p><p>生成的结果为:<br><figure class="highlight html"><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="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">th:src</span>=<span class="string">"/webjars/jquery/2.1.4/jquery.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br></pre></td></tr></table></figure></p><p>原理很简单,运行时使用<code>ResourceUrlEncodingFilter</code>这个过滤器,将<code>response</code>通过<code>HttpServletResponseWrapper</code>进行静态包装并重写<code>encodeURL</code>方法,然后模板中使用<code>response</code>的<code>encodeURL</code>将链接地址重写。并且一定要<strong>注意</strong>,<code>Thymeleaf</code> 和 <code>FreeMarker</code>这两个模板自动配置了<code>ResourceUrlEncodingFilter</code>,JSP则需要手动声明这个过滤器,而其它模板则需要自已去实现这个功能。下面说到的其它版本管理中,也是同理。</p><p>此时,如果想要更换jquery版本号,只需修改jquery的webjars的dependency版本即可。</p><h3 id="2-2-缓存清除"><a href="#2-2-缓存清除" class="headerlink" title="2.2.缓存清除"></a>2.2.缓存清除</h3><p>当静态资源发生变化时,为了清除用户客户端的缓存,我们一般会将静态资源加上版本号或者固定时间戮,每当资源变化时手动去更新版本或时间戳。比如:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">type</span>=<span class="string">"text/css"</span> <span class="attr">href</span>=<span class="string">"/css/style.css?v=1.0.0"</span>/></span></span><br></pre></td></tr></table></figure><p>这种方式也是需要我们一个页面一个页面的去修改,显然不合适。我们有更好的方式,Spring Boot中我们可以去配置<code>VersionResourceResolver</code>,它有两种策略:</p><ul><li>FixedVersionStrategy可以使用某项属性,或者日期,或者其它来作为版本.</li><li>ContentVersionStrategy是使用资源内容计算出来的MD5哈希作为版本</li></ul><p>与Webjars添加动态版本号一样,这两种方式都借助了<code>ResourceUrlEncodingFilter</code>来重写模板中的URL。</p><p>更详细配置可参考:Spring Framework’s<a href="http://docs.spring.io/spring/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#mvc-config-static-resources" title="Spring MVC的官方文档"> reference documentation.</a></p><h4 id="2-2-1-ContentVersionStrategy"><a href="#2-2-1-ContentVersionStrategy" class="headerlink" title="2.2.1.ContentVersionStrategy"></a>2.2.1.ContentVersionStrategy</h4><p>properties文件配置方法如下:<br><figure class="highlight plain"><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">spring.resources.chain.strategy.content.enabled=true</span><br><span class="line">spring.resources.chain.strategy.content.paths=/**</span><br></pre></td></tr></table></figure></p><p>同样也可以在继承<code>WebMvcConfigurerAdapter</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addResourceHandlers</span><span class="params">(ResourceHandlerRegistry registry)</span> </span>{</span><br><span class="line"> registry.addResourceHandler(<span class="string">"/**"</span>)</span><br><span class="line"> .addResourceLocations(<span class="string">"classpath:/static/"</span>)</span><br><span class="line"> .resourceChain(<span class="keyword">true</span>)</span><br><span class="line"> .addResolver(<span class="keyword">new</span> VersionResourceResolver().addContentVersionStrategy(<span class="string">"/**"</span>));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>配置成功后,页面中的:<br><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">type</span>=<span class="string">"text/css"</span> <span class="attr">th:href</span>=<span class="string">"@{/css/style.css}"</span>/></span></span><br></pre></td></tr></table></figure></p><p>会被替换为:<br><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">type</span>=<span class="string">"text/css"</span> <span class="attr">th:href</span>=<span class="string">"/css/style-2a2d595e6ed9a0b24f027f2b63b134d6.css"</span>/></span></span><br></pre></td></tr></table></figure></p><p>文件的hash会被缓存,使用的是Spring的<code>ConcurrentMapCache</code>,一个简单的缓存实现,文件改动后,只有重启服务再次访问才会重新生成hash</p><h4 id="2-2-2-FixedVersionStrategy"><a href="#2-2-2-FixedVersionStrategy" class="headerlink" title="2.2.2.FixedVersionStrategy"></a>2.2.2.FixedVersionStrategy</h4><p>在动态的加载静态资源时,比如javascript模块加截器,这时更改文件名不是个好选择,可以使用<code>FixedVersionStrategy</code>,用某项属性,或者日期,或者其它来作为版本。</p><p>比如,在properties文件中这样配置<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">spring.resources.chain.strategy.content.enabled=true</span><br><span class="line">spring.resources.chain.strategy.content.paths=/**</span><br><span class="line">spring.resources.chain.strategy.fixed.enabled=true</span><br><span class="line">spring.resources.chain.strategy.fixed.paths=/js/lib/</span><br><span class="line">spring.resources.chain.strategy.fixed.version=v12</span><br></pre></td></tr></table></figure></p><p>这样配置后, 位于 <code>"/js/lib/"</code>下的 JavaScript 将会使用一个固定的版本号<code>"/v12/js/lib/mymodule.js"</code> ,而其它的静态资源仍然使用文件hash的方式。</p><p>同样可以在类中配置:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addResourceHandlers</span><span class="params">(ResourceHandlerRegistry registry)</span> </span>{</span><br><span class="line"> registry.addResourceHandler(<span class="string">"/**"</span>)</span><br><span class="line"> .addResourceLocations(<span class="string">"classpath:/static/"</span>)</span><br><span class="line"> .resourceChain(<span class="keyword">true</span>)</span><br><span class="line"> .addResolver(<span class="keyword">new</span> VersionResourceResolver()</span><br><span class="line"> .addFixedVersionStrategy(<span class="string">"v12"</span>, <span class="string">"/js/lib/"</span>)</span><br><span class="line"> .addContentVersionStrategy(<span class="string">"/**"</span>));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h3 id="2-3-URL中含有”-”字符的问题"><a href="#2-3-URL中含有”-”字符的问题" class="headerlink" title="2.3.URL中含有”#”字符的问题"></a>2.3.URL中含有”#”字符的问题</h3><p>查看<code>ResourceUrlEncodingFilter</code>这个过滤器,发现当URL中含有”?”,比如<br><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">th:src</span>=<span class="string">"@{/jquery.js?v=1}"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure></p><p><code>response</code>的<code>encodeURL</code>执行时,会首先将”?”后面的部分<code>?v=1</code>去掉,然后再根据<code>/jquery.js</code>去资源目录去查找文件<br><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="function"><span class="keyword">private</span> <span class="keyword">int</span> <span class="title">getQueryParamsIndex</span><span class="params">(String url)</span> </span>{</span><br><span class="line"><span class="keyword">int</span> index = url.indexOf(<span class="string">"?"</span>);</span><br><span class="line"><span class="keyword">return</span> (index > <span class="number">0</span> ? index : url.length());</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>如果URL中有其它字符,比如<code>/jquery.js#1</code>,<code>encodeURL</code>执行时会拿着<code>/jquery.js#1</code>去查找文件,这样当然找不到,所以导致没法配置资源缓存清除这个功能了。</p><p>至于为什么我会遇到资源文件的URL中有”#”字符的情况呢?<br><figure class="highlight html"><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="tag"><<span class="name">svg</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">use</span> <span class="attr">xlink:href</span>=<span class="string">"/svg-symbols.svg#grbs"</span>></span><span class="tag"></<span class="name">use</span>></span></span><br><span class="line"><span class="tag"></<span class="name">svg</span>></span></span><br></pre></td></tr></table></figure></p><p>我们公司前端的svg引用就是这样写的,将很多svg放在一个文件中,通过”#”选择调用。</p><p>于是我重写了<code>ResourceUrlEncodingFilter</code>过滤器,添加”#”的判断,并优化了文件URL缓存。</p>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> static </tag>
<tag> webjars </tag>
<tag> springboot </tag>
</tags>
</entry>
<entry>
<title>Spring Boot去除provided的依赖</title>
<link href="/java/Spring-Boot-exclude-dependency.html"/>
<url>/java/Spring-Boot-exclude-dependency.html</url>
<content type="html"><![CDATA[<p>Maven的Dependency scope是用来限制Dependency的作用范围的, 影响maven项目在各个生命周期时导入的package的状态。总共有6种范围,默认是<code>compile</code>,作用于整个生命周期。当scope设置为<code>provided</code>时,打包的时候可以不用包进去。该依赖理论上可以参与编译,测试,运行等周期,相当于compile,但是在打包阶段做了exclude的动作。</p><p>例如,Java EE中Servlet api,我们在编译的时候用到servlet-api和jsp-api,但在打包的时候不用这两个依赖,因为容器都有提供。</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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">groupId</span>></span>javax.servlet<span class="tag"></<span class="name">groupId</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">artifactId</span>></span>servlet-api<span class="tag"></<span class="name">artifactId</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">version</span>></span>2.5<span class="tag"></<span class="name">version</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">scope</span>></span>provided<span class="tag"></<span class="name">scope</span>></span> </span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">groupId</span>></span>javax.servlet.jsp<span class="tag"></<span class="name">groupId</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">artifactId</span>></span>jsp-api<span class="tag"></<span class="name">artifactId</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">version</span>></span>2.1<span class="tag"></<span class="name">version</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">scope</span>></span>provided<span class="tag"></<span class="name">scope</span>></span> </span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><p>但是在spring boot当中,当Dependency scope设置为<code>provided</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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">artifactId</span>></span>spring-boot-devtools<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">optional</span>></span>true<span class="tag"></<span class="name">optional</span>></span></span><br><span class="line"><span class="tag"><<span class="name">scope</span>></span>provided<span class="tag"></<span class="name">scope</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><a id="more"></a> <p><code>spring-boot-devtools</code>这个依赖帮助我们在开发的时候使用LiveReload或者Hot swapping,但是正式环境中用不到它(也有用但是我用不着),所以就没必要将这个依赖打入jar中,我将其设置为<code><scope>provided</scope></code>,但是生成executable jar中依旧含有这个jar包。</p><p><img src="/java/Spring-Boot-exclude-dependency/spring_boot_devtools_in_jar.png" alt="Spring-Boot-exclude-dependency"></p><p>我们使用Spring Boot时,都会使用<code>Spring Boot Maven Plugin</code>插件,其中的一个作用通过<code>spring-boot:repackage</code>生成一个可执行的jar或者war。一个可执行的jar当中必然包含servlet容器,如默认的tomcat,所以<code>repackage</code>设计成:即使dependencies的scope为<code>provided</code>,依旧会将依赖打入jar当中。</p><p>为了可以excluded dependencies from the executable jar,所以<code>Spring Boot Maven Plugin</code>可以通过配置去排除依赖<a href="http://docs.spring.io/spring-boot/docs/current/maven-plugin/examples/exclude-dependency.html" title="exclude dependency">exclude dependency</a></p><ul><li>通过特定的<code>groupId</code>和<code>artifactId</code>来排除 (有的依赖还有<code>classifier</code>)</li><li>通过指定<code>artifactId</code>来排除:任何符合给定的<code>artifactId</code>都被排除</li><li>通过指定<code>groupId</code>来排除属于这个<code>groupId</code>下的依赖</li></ul><p>排除特定依赖<code>com.foo:bar</code>:<br><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><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">project</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"><<span class="name">build</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"><<span class="name">plugins</span>></span></span><br><span class="line"> ...</span><br><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.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-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>1.4.2.RELEASE<span class="tag"></<span class="name">version</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">excludes</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">exclude</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.foo<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>bar<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">exclude</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">excludes</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">configuration</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"></<span class="name">plugin</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"></<span class="name">plugins</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"></<span class="name">build</span>></span></span><br><span class="line"> ...</span><br><span class="line"><span class="tag"></<span class="name">project</span>></span></span><br></pre></td></tr></table></figure></p><p>排除artifact为<code>my-lib</code> 或者 <code>another-lib</code>的依赖<br><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><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">project</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"><<span class="name">build</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"><<span class="name">plugins</span>></span></span><br><span class="line"> ...</span><br><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.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-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>1.4.2.RELEASE<span class="tag"></<span class="name">version</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">excludeArtifactIds</span>></span>my-lib,another-lib<span class="tag"></<span class="name">excludeArtifactIds</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">configuration</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"></<span class="name">plugin</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"></<span class="name">plugins</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"></<span class="name">build</span>></span></span><br><span class="line"> ...</span><br><span class="line"><span class="tag"></<span class="name">project</span>></span></span><br></pre></td></tr></table></figure></p><p>排除所有属于<code>com.foo</code>这个group的依赖<br><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><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">project</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"><<span class="name">build</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"><<span class="name">plugins</span>></span></span><br><span class="line"> ...</span><br><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.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-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>1.4.2.RELEASE<span class="tag"></<span class="name">version</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">excludeGroupIds</span>></span>com.foo<span class="tag"></<span class="name">excludeGroupIds</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">configuration</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"></<span class="name">plugin</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"></<span class="name">plugins</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"></<span class="name">build</span>></span></span><br><span class="line"> ...</span><br><span class="line"><span class="tag"></<span class="name">project</span>></span></span><br></pre></td></tr></table></figure></p>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> springboot </tag>
<tag> maven </tag>
<tag> provided </tag>
<tag> dependency </tag>
</tags>
</entry>
<entry>
<title>SpringBoot使用thymeleaf 3</title>
<link href="/java/Spring-Boot-thymeleaf-3.html"/>
<url>/java/Spring-Boot-thymeleaf-3.html</url>
<content type="html"><![CDATA[<p>如果你的spring boot应用继承<code>spring-boot-starter-parent</code>,那么只需要添加<code>spring-boot-starter-thymeleaf</code>这个starter依赖,即可使用thymeleaf模板引擎</p><pre><code><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId></dependency></code></pre><a id="more"></a> <h2 id="升级为thymeleaf-3"><a href="#升级为thymeleaf-3" class="headerlink" title="升级为thymeleaf 3"></a>升级为thymeleaf 3</h2><p>从<code>spring-boot-dependencies</code>中的<code>dependencyManagement</code>中可以看到:<code>spring-boot-starter-thymeleaf</code>,默认使用Thymeleaf 2.1</p><p><img src="/java/Spring-Boot-thymeleaf-3/Thymeleaf2.1.png" alt="jodd_beancopy_converter"></p><p>如果要改为Thymeleaf 3,只需要重写<code>thymeleaf.version</code>和<code>thymeleaf-layout-dialect.version</code>两个properties</p><pre><code><properties> <thymeleaf.version>3.0.2.RELEASE</thymeleaf.version> <thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version></properties></code></pre><h2 id="thymeleaf-3"><a href="#thymeleaf-3" class="headerlink" title="thymeleaf 3"></a>thymeleaf 3</h2><p>Thymeleaf3完全兼容Thymeleaf2,升级到Thymeleaf3的过程中,我只用到的部分特性,参考地址:<a href="http://www.thymeleaf.org/doc/articles/thymeleaf3migration.html" title="Thymeleaf 3 迁移指南">Thymeleaf 3 迁移指南</a>。</p><h3 id="1-Full-HTML5-markup-support(完整的HTML5-标记支持)"><a href="#1-Full-HTML5-markup-support(完整的HTML5-标记支持)" class="headerlink" title="1.Full HTML5 markup support(完整的HTML5 标记支持)"></a>1.Full HTML5 markup support(完整的HTML5 标记支持)</h3><p>Thymeleaf 2.1中,html代码必须严格遵守XML规范,必须是<strong>XML-well-formed HTML5 code</strong>,比如:</p><ol><li>标签必须闭合,<code><br></code> 是错误的</li><li>属性必须有值,<code><span v-cloak/></code> 是不被允许的</li></ol><p>不是所有的人都会完全的遵守XML规范,Thymeleaf2中要解决这个问题,可以将<code>spring.thymeleaf.mode</code>这个属性改为<code>LEGACYHTML5</code>,然后添加<code>nekoHTML</code>这个库。如果使用Thymeleaf3,就不会存在这个问题,因为Thymeleaf3使用新的解析引擎。</p><h3 id="2-Template-modes(模板类型)"><a href="#2-Template-modes(模板类型)" class="headerlink" title="2.Template modes(模板类型)"></a>2.Template modes(模板类型)</h3><ul><li>HTML、XML、TEXT、JAVASCRIPT、CSS、RAW</li></ul><p>分为三类:标记型模板(HTML,XML),文本型模板(TEXT, JAVASCRIPT和CSS),无操作(no-op)模板 (RAW)。</p><ul><li>Thymeleaf2.1中的<code>HTML5</code>, <code>XHTML</code>, <code>VALIDXHTML</code>和<code>LEGACYHTML5</code>相当于3.0中的 <code>HTML</code></li><li>Thymeleaf2.1中的<code>VALIDXML</code>也就是3.0中的<code>XML</code></li></ul><p>所以在Thymeleaf3中使用<code>HTML</code>包括了HTML5,HTML4和XHTML在内的所有类型的HTML标记,此时,标记的作用范围按可能的最大化处理。</p><p>spring boot需要将默认的<code>HTML5</code>改为<code>HTML</code>,在application.properties文件中增加:<code>spring.thymeleaf.mode=HTML</code></p><h5 id="Improved-inlining-mechanism(增强的内联机制)"><a href="#Improved-inlining-mechanism(增强的内联机制)" class="headerlink" title="Improved inlining mechanism(增强的内联机制)"></a>Improved inlining mechanism(增强的内联机制)</h5><p>Thymeleaf3中可无需额外的标签,直接在文本中输出数据</p><pre><code><p>This product is called [[${product.name}]] and it's great!</p></code></pre><p>Thymeleaf2.1中则需要使用内联标签<code>th:inline</code></p><pre><code><p th:inline="text"> This product is called [[${product.name}]] and it's great!</p></code></pre><p>上面的代码中也可以使用<code>[(${product.name)]</code>来代替,<code>[[...]]</code>和<code>[(...)]</code>区别在于<code>[(...)]</code>中的文本不会被Escape,就相当于<code>th:text</code>和<code>th:utext</code>的区别</p><h3 id="3-Fragment-Expressions(片段表达式)"><a href="#3-Fragment-Expressions(片段表达式)" class="headerlink" title="3.Fragment Expressions(片段表达式)"></a>3.Fragment Expressions(片段表达式)</h3><p>Thymeleaf 3.0 引入了一个新的Fragment Expressions。像是这样:<code>~{commons::footer}</code>。例如,我们定义一个模版页面base.html</p><pre><code><head th:fragment="common_header(title,links)"> <title th:utext="${title}">The awesome application</title> <!-- Common styles and scripts --> <link rel="stylesheet" type="text/css" media="all" th:href="@{/css/awesomeapp.css}"> <link rel="shortcut icon" th:href="@{/images/favicon.ico}"> <script type="text/javascript" th:src="@{/sh/scripts/codebase.js}"></script> <!--/* Per-page placeholder for additional links */--> <th:block th:replace="${links} ?: ~{}" /></head></code></pre><p>在我们页面中使用这个模板</p><pre><code>...<head th:replace="base :: common_header(~{::title},~{::link})"> <title>Awesome - Main</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> <link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}"></head>...</code></pre><p>结果会输出:</p><pre><code>...<head> <title>Awesome - Main</title> <!-- Common styles and scripts --> <link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css"> <link rel="shortcut icon" href="/awe/images/favicon.ico"> <script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script> <link rel="stylesheet" href="/awe/css/bootstrap.min.css"> <link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css"></head>...</code></pre><p>这个特性解决了通用的header和footer的问题,详细说明参考:<a href="https://github.com/thymeleaf/thymeleaf/issues/451" title="Fragment Expressions">https://github.com/thymeleaf/thymeleaf/issues/451</a></p><h3 id="4-Decoupled-Template-Logic(模板逻辑解耦)"><a href="#4-Decoupled-Template-Logic(模板逻辑解耦)" class="headerlink" title="4.Decoupled Template Logic(模板逻辑解耦)"></a>4.Decoupled Template Logic(模板逻辑解耦)</h3><p>定义一个完全的html模版<code>home.html</code></p><pre><code><!DOCTYPE html><html> <body> <table id="usersTable"> <tr> <td class="username">Jeremy Grapefruit</td> <td class="usertype">Normal User</td> </tr> <tr> <td class="username">Alice Watermelon</td> <td class="usertype">Administrator</td> </tr> </table> </body></html></code></pre><p>然后只需要定义一个<code>home.th.xml</code></p><pre><code><?xml version="1.0"?><thlogic> <attr sel="#usersTable" th:remove="all-but-first"> <attr sel="/tr[0]" th:each="user : ${users}"> <attr sel="td.username" th:text="${user.name}" /> <attr sel="td.usertype" th:text="#{|user.type.${user.type}|}" /> </attr> </attr></thlogic></code></pre><p>这样前端人员写的html与后端代码完全解耦,详细说明参考:<a href="https://github.com/thymeleaf/thymeleaf/issues/465" title="Decoupled template logic">https://github.com/thymeleaf/thymeleaf/issues/465</a></p>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> springboot </tag>
<tag> thymeleaf </tag>
</tags>
</entry>
<entry>
<title>给jodd的beancopy注册Converter</title>
<link href="/java/jodd_beancopy_converter.html"/>
<url>/java/jodd_beancopy_converter.html</url>
<content type="html"><![CDATA[<p>公司的项目是微服务架构,使用facebook的thrift框架作为微服务之间rpc通信的协议,thrift是一个跨语言的服务框架。</p><blockquote><p>Thrift通过接口定义语言 (interface definition language,IDL) 来定义数据类型和服务,Thrift接口定义文件由Thrift代码编译器生成thrift目标语言的代码。</p></blockquote><p>Thrift编译器生成API文件,我们分别实现其server与client。在server端,将服务需要响应的数据(如数据库中查询所得)封装到thrift生成的java对象中序列化后返回,在client端收到之后,反序列化成thrift生成的java对象。thrift框架的序列化及反序列化使用的binary protocol,即二进制协议,速度快,性能高。</p><a id="more"></a> <h2 id="BeanCopy"><a href="#BeanCopy" class="headerlink" title="BeanCopy"></a>BeanCopy</h2><p>我们在使用Thrift的过程中,需要使用到两次java对象属性的复制:server端再将对象复制到thrift生成的java对象中,client端将thrift对象复制到自己的定义的java对象中。java中使用bean copy的工具类,比较多是的jodd的beanCopy与Apache的BeanUtils,我们用的jodd。将A对象复制到B对象,jodd的BeanCopy使用如下:</p><pre><code>BeanCopy.beans(A, B).copy();</code></pre><p>然而由于thrift框架的基本数据类型中,没有java中的时间类型,即<code>java.util.Date</code>,我们会将时间定义为String或long类型。比较推荐使用long类型时间戳,但是之前代码中都使用的String类型,比较令人讨厌,旧的项目中大量的使用BeanCopy,这时候改动比较麻烦。</p><p>那么使用String来表示时间,配置BeanCopy的Converter。</p><h2 id="Converter"><a href="#Converter" class="headerlink" title="Converter"></a>Converter</h2><p>jodd中类型之间的转换大量的使用Converter,并且有一个”类型转换管理器”:<code>jodd.typeconverter.TypeConverterManager</code>。<code>TypeConverterManager</code>注册了大量的转换器。<br><img src="/java/jodd_beancopy_converter/typeConverterManager_register.png" alt="jodd_beancopy_converter"></p><p>将Date转化为String,使用的是<code>jodd.typeconverter.impl.StringConverter</code></p><p><img src="/java/jodd_beancopy_converter/StringConverter.png" alt="jodd_beancopy_converter"></p><p>原理很简单,先判断被转换对象的类型,然后实现不同对象的转换,Date转换为String使用的是Date的toString方法</p><p><img src="/java/jodd_beancopy_converter/date_toString.png" alt="jodd_beancopy_converter"></p><p>被转化的String格式为:<code>"EEE MMM dd HH:mm:ss zzz yyyy"</code>,且Locale为US</p><p>jodd中的使用<code>jodd.typeconverter.impl.DateConverter</code>将对象转化为Date</p><p><img src="/java/jodd_beancopy_converter/DateConverter.png" alt="jodd_beancopy_converter"></p><p>使用的默认格式为:<code>YYYY-MM-DD hh:mm:ss.mss</code> ,这时候我们有两种解决方案。</p><ul><li>在server端重新注册StringConverter,BeanCopy时就将Date转为<code>YYYY-MM-DD hh:mm:ss.mss</code>格式的String,client端会被转换为Date</li><li>在client端重新注册DateConverter,BeanCopy时将”EEE MMM dd HH:mm:ss zzz yyyy”格式的String转换为Date</li></ul><p>DateConverter(项目启动后,执行即可):<br><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">TypeConverterManager.register(Date.class, value -> {</span><br><span class="line"> <span class="keyword">if</span> (value == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (value <span class="keyword">instanceof</span> Date) {</span><br><span class="line"> <span class="keyword">return</span> value;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (value <span class="keyword">instanceof</span> Number) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Date(((Number) value).longValue());</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> String stringValue = value.toString().trim();</span><br><span class="line"> SimpleDateFormat slf = <span class="keyword">new</span> SimpleDateFormat(<span class="string">"EEE MMM dd HH:mm:ss zzz yyyy"</span>, Locale.US);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">return</span> slf.parse(stringValue);</span><br><span class="line"> } <span class="keyword">catch</span> (ParseException e) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure></p><p>StringConverter类似,只需要增加一个Date类型的判断:<br><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">if</span> (value <span class="keyword">instanceof</span> Date) {</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> SimpleDateFormat(<span class="string">"YYYY-MM-DD hh:mm:ss.mss"</span>).format(value);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h2 id="thrift的binary类型"><a href="#thrift的binary类型" class="headerlink" title="thrift的binary类型"></a>thrift的binary类型</h2><p>公司有的项目直接使用thrift的binary做数据的返回类型,java中对应的是ByteBuffer。</p><p>这时候我们需要自己实现对象的序列化与反序列化,比如说MessagePack,一种比JSON更快、更小的格式</p><p>这时候就可以不用thrift生成的java对象了,也就不需要烦恼任何类型转换的问题了。</p>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> springboot </tag>
<tag> jodd </tag>
<tag> beancopy </tag>
</tags>
</entry>
<entry>
<title>批量更新Git代码库</title>
<link href="/git/gitUpdater.html"/>
<url>/git/gitUpdater.html</url>
<content type="html"><![CDATA[<p>每天上班前会所有的代码git pull,保持项目是最新的,但是由于公司使用的架构(thrift微服务),导致项目特别多,一个项目一个项目的git pull挺麻烦的,于是在想有没有一个好的办法,一次性更新所有的项目。于是在网上搜索发现了<a href="https://github.com/earwig/git-repo-updater" title="gitup">git-repo-updater</a>这个小工具。<br><a id="more"></a><br><strong>gitup</strong> (the <em>git-repo-updater</em>)</p><p>gitup is a tool designed to update a large number of git repositories at once.<br>It is smart enough to handle multiple remotes, branches, dirty working<br>directories, and more, hopefully providing a great way to get everything<br>up-to-date for short periods of internet access between long periods of none.</p><p>gitup should work on OS X, Linux, and Windows. You should have the latest<br>version of git and either Python 2.7 or Python 3 installed.<br><!-- more --> </p><h1 id="Installation"><a href="#Installation" class="headerlink" title="Installation"></a>Installation</h1><p>With <a href="http://brew.sh/">Homebrew</a>:</p><pre><code>brew install gitup</code></pre><h2 id="From-source"><a href="#From-source" class="headerlink" title="From source"></a>From source</h2><p>First:</p><pre><code>git clone git://github.com/earwig/git-repo-updater.gitcd git-repo-updater</code></pre><p>Then, to install for everyone:</p><pre><code>sudo python setup.py install</code></pre><p>…or for just yourself (make sure you have <code>~/.local/bin</code> in your PATH):</p><pre><code>python setup.py install --user</code></pre><p>Finally, simply delete the <code>git-repo-updater</code> directory, and you’re done!</p><p><strong>Note:</strong> If you are using Windows, you may wish to add a macro so you can<br>invoke gitup in any directory. Note that <code>C:\python27\</code> refers to the<br>directory where Python is installed:</p><pre><code>DOSKEY gitup=c:\python27\python.exe c:\python27\Scripts\gitup $*</code></pre><h1 id="Usage"><a href="#Usage" class="headerlink" title="Usage"></a>Usage</h1><p>There are two ways to update repos: you can pass them as command arguments,<br>or save them as “bookmarks”.</p><p>For example:</p><pre><code>gitup ~/repos/foo ~/repos/bar ~/repos/baz</code></pre><p>will automatically pull to the <code>foo</code>, <code>bar</code>, and <code>baz</code> git repositories.<br>Additionally, you can just type:</p><pre><code>gitup ~/repos</code></pre><p>to automatically update all git repositories in that directory.</p><p>To add a bookmark (or bookmarks), either of these will work:</p><pre><code>gitup --add ~/repos/foo ~/repos/bar ~/repos/bazgitup --add ~/repos</code></pre><p>Then, to update all of your bookmarks, just run gitup without args:</p><pre><code>gitup</code></pre><p>Delete a bookmark:</p><pre><code>gitup --delete ~/repos</code></pre><p>View your current bookmarks:</p><pre><code>gitup --list</code></pre><p>You can mix and match bookmarks and command arguments:</p><pre><code>gitup --add ~/repos/foo ~/repos/bargitup ~/repos/baz # update 'baz' onlygitup # update 'foo' and 'bar' onlygitup ~/repos/baz --update # update all three!</code></pre><p>Update all git repositories in your current directory:</p><pre><code>gitup .</code></pre><p>By default, gitup will fetch all remotes in a repository. Pass <code>--current-only</code><br>(or <code>-c</code>) to make it fetch <em>only</em> the remote tracked by the current branch.</p><p>Also by default, gitup will try to fast-forward all branches that have<br>upstreams configured. It will always skip branches where this is not possible<br>(e.g. dirty working directory or a merge/rebase is required). Pass<br><code>--fetch-only</code> (or <code>-f</code>) to only fetch remotes.</p><p>After fetching, gitup will <em>keep</em> remote-tracking branches that no longer exist<br>upstream. Pass <code>--prune</code> (or <code>-p</code>) to delete them, or set <code>fetch.prune</code> or<br><code>remote.<name>.prune</code> in git config to do this by default.</p><p>For a full list of all command arguments and abbreviations:</p><pre><code>gitup --help</code></pre><p>Finally, all paths can be either absolute (e.g. <code>/path/to/repo</code>) or relative<br>(e.g. <code>../my/repo</code>).</p>]]></content>
<categories>
<category> git </category>
</categories>
<tags>
<tag> git </tag>
</tags>
</entry>
<entry>
<title>First Post ~</title>
<link href="/blog/firstpost.html"/>
<url>/blog/firstpost.html</url>
<content type="html"><![CDATA[<p><img src="/blog/firstpost/wordpress.png" alt="worpress"></p><h1 id="Wordpress"><a href="#Wordpress" class="headerlink" title="Wordpress"></a>Wordpress</h1><p>2010年夏天开始学习并使用Wordpress写博客,已经有好几年了,当时还是在学校里面,学的是与计算机无关的专业。一开始接触的程序是学校的Discuz论坛,那时就对网站有关的知识产生了极大了兴趣,通过网络认识了Wordpress这个程序,然后开始慢慢的折腾,对网站的一些概念才有了一定的认识,所以才有了后来转到计算机专业的念头。</p><a id="more"></a> <p>当然由于一些原因,除了最开始的几年我会经常的去折腾主题,折腾特效,觉得蛮有意思外,后面几乎没有花时间在上面了。wordpress是php写,我使用的是最便宜的虚拟主机,运行的环镜:<code>Apache+php+Mysql</code>。一年的费用是100元。</p><h1 id="Hexo"><a href="#Hexo" class="headerlink" title="Hexo"></a>Hexo</h1><p>前几天开始尝试使用hexo与markdown做一个静态博客,Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。</p><p>并将博客托管到github pages上.<br><img src="/blog/firstpost/github.jpg" alt="github"></p><p>markdown还是挺有用的,最近也在研究使用gitbook写交流文档。</p><p><a href="http://book.imyxiao.com">http://book.imyxiao.com</a></p>]]></content>
<categories>
<category> blog </category>
</categories>
<tags>
<tag> firstpost </tag>
<tag> hexo </tag>
<tag> wordpress </tag>
</tags>
</entry>
<entry>
<title>Hello World</title>
<link href="/blog/hello-world.html"/>
<url>/blog/hello-world.html</url>
<content type="html"><![CDATA[<p>Welcome to <a href="https://hexo.io/">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues">GitHub</a>.</p><h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new <span class="string">"My New Post"</span></span><br></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/writing.html">Writing</a></p><h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo server</span><br></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/server.html">Server</a></p><h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo generate</span><br></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/generating.html">Generating</a></p><h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/deployment.html">Deployment</a></p>]]></content>
<categories>
<category> blog </category>
</categories>
<tags>
<tag> hello </tag>
</tags>
</entry>
</search>