Jekyll2019-11-26T02:35:43+00:00http://springboot.javaboy.org/feed.xmlSpring Boot2 教程合集Spring Boot2 原创教程合集,还在不断更新中 配套视频教程同步发布!
江南一点雨wangsong0210@gmail.com给大家整理了几个开源免费的 Spring Boot + Vue 学习资料2019-11-20T09:52:13+00:002019-11-20T09:52:13+00:00http://springboot.javaboy.org/2019/1120/springboot-vue<p>最近抽空在整理前面的文章案例啥的,顺便把手上的几个 Spring Boot + Vue 的学习资料推荐给各位小伙伴。这些案例有知识点的讲解,也有项目实战,正在做这一块的小伙伴们可以收藏下。</p>
<!--more-->
<h2 id="案例学习">案例学习</h2>
<h3 id="javaboy-video-samples">javaboy-video-samples</h3>
<ul>
<li>项目地址:https://github.com/lenve/javaboy-video-samples</li>
</ul>
<p>这个是松哥录制的 Spring Boot2 系列视频教程的案例,视频是加密的,但是案例从一开始就是开源的,这个可以毫无保留的共享给大家,案例可以说是非常全面,这个仓库会随着视频的录制而继续完善。</p>
<p><img src="http://www.javaboy.org/images/other/88-1.png" alt="" /></p>
<h3 id="javaboy-code-samples">javaboy-code-samples</h3>
<ul>
<li>项目地址:https://github.com/lenve/javaboy-code-samples</li>
</ul>
<p>这个是我平时公众号上文章的案例,因为公众号的文章大部分都是以 Spring Boot + Vue 前后端分离开发为主,所以这些文章也是这一方面的,不同于上面的那个仓库,这里的每个案例都有对应的文章进行讲解,这个仓库中的内容会随着公众号文章的增加而继续增加:</p>
<p><img src="http://www.javaboy.org/images/other/88-2.png" alt="" /></p>
<h3 id="spring-boot-vue-samples">spring-boot-vue-samples</h3>
<ul>
<li>项目地址:https://github.com/lenve/spring-boot-vue-samples</li>
</ul>
<p>这个是松哥《Spring Boot + Vue 全栈开发实战》一书的官方案例,但是因为书里给出来的地址是一个百度云盘的地址,所以这个仓库很少受到小伙伴们的关注。不过这套案例的整理的不是很满意,另外这套案例也比较旧了,是去年的,所以建议小伙伴们关注第一个,里边的案例比较新,也比较整齐。</p>
<p><img src="http://www.javaboy.org/images/other/88-3.png" alt="" /></p>
<h3 id="spring-boot2">Spring Boot2</h3>
<ul>
<li>项目地址:http://springboot.javaboy.org</li>
</ul>
<p>这个不是 GitHub 上的仓库,是松哥一系列 Spring Boot 相关文章的集合,我把它做成了电子书的形式,但是有两个不太满意的地方:一个是文章排版不太满意,另一个则是网站托管在国外服务器上,访问速度较慢,因此这个在线的电子书我最近也在整理,整理完成后,我会分享出来给大家免费下载,小伙伴们多多关注公众号上的消息哦。</p>
<p><img src="http://www.javaboy.org/images/other/88-4.png" alt="" /></p>
<h3 id="awesome-github-vue">awesome-github-vue</h3>
<ul>
<li>项目地址:https://github.com/opendigg/awesome-github-vue</li>
</ul>
<p>这个不是松哥的仓库,这是我刚开始学习 Vue 的时候收藏的一个仓库,感觉非常棒,很多常用的 Vue 插件这里都有,不过稍微遗憾的是,这个仓库有两年没有更新了,不过对于刚刚开始接触 Vue 的小伙伴而言,这个仓库够用了。</p>
<p><img src="http://www.javaboy.org/images/other/88-5.png" alt="" />
<img src="http://www.javaboy.org/images/other/88-6.png" alt="" />
<img src="http://www.javaboy.org/images/other/88-7.png" alt="" /></p>
<h2 id="项目">项目</h2>
<p>项目就不用多说了,V 部落和微人事。</p>
<h3 id="微人事">微人事</h3>
<ul>
<li>项目地址:https://github.com/lenve/vhr</li>
</ul>
<p>关于微人事我已经写过好多文章了,这里就不再赘述了,要告诉大家的一个好消息是,大概在 12 月,微人事会进行一次全面的版本升级,Spring Boot 切换到当前最新版,Vue 构建工具切换到 vue-cli3,而且还会引入消息中间件 RabbitMQ 等一些外部工具,进一步扩展微人事所涉及到的知识点。微人事项目最新体验地址:</p>
<ul>
<li><a href="http://vhr.itboyhub.com">http://vhr.itboyhub.com</a></li>
</ul>
<p>当然,松哥之前也录过一个微人事部署视频,大家可以参考:</p>
<h3 id="v-部落">V 部落</h3>
<ul>
<li>项目地址:https://github.com/lenve/VBlog</li>
</ul>
<p>V 部落没有微人事那么丰富的文档,但是比较简单,业务简单,用到的技术点也简单,不过好多小伙伴竟然反映部署不起来,以后我看时间抽空也可以录一个部署教程吧,V 部落目前最新的体验地址是:</p>
<ul>
<li><a href="http://vblog.itboyhub.com">http://vblog.itboyhub.com</a></li>
</ul>
<p>最后再悄悄告诉大家,公众号后台回复 <code class="language-plaintext highlighter-rouge">2TB</code> 可以获取 2TB Java 学习资源下载地址哦。</p>江南一点雨wangsong0210@gmail.com最近抽空在整理前面的文章案例啥的,顺便把手上的几个 Spring Boot + Vue 的学习资料推荐给各位小伙伴。这些案例有知识点的讲解,也有项目实战,正在做这一块的小伙伴们可以收藏下。2TB 学习资源2019-11-12T08:35:27+00:002019-11-12T08:35:27+00:00http://springboot.javaboy.org/2019/1112/java-video<p>今年 5 月份的时候,松哥发了一个视频资源库,当时和大家说,这个资源库会定期更新,后来却迟迟未更新,其实不是我没资源了,是因为当时的关键字是我一个一个在微信后台配置的,配置到后面发现,后台配置关键字有数量上限,没法继续配置了,所以这事就搁置下来了。</p>
<!--more-->
<p>九月份松哥上线了自己的服务,和微信的后台对接起来,具体实现大家可以参考这两篇文章:</p>
<ul>
<li><a href="https://mp.weixin.qq.com/s/f3QexxLp9vT6aE1Pl3jHGw">Spring Boot 开发微信公众号后台</a></li>
<li><a href="https://mp.weixin.qq.com/s/1jTl9kBeFeibjbe5EbsCzg">Spring Boot 如何给微信公众号返回消息</a></li>
</ul>
<p>现在再配置关键字就没有限制了。于是最近抽空把资源更新了一波,废话不多说,大家在公众号【江南一点雨】后台回复相应的口令,就可以获取相应的视频下载地址。</p>
<h2 id="java-基础">Java 基础</h2>
<table>
<thead>
<tr>
<th style="text-align: left">资源名称</th>
<th>口令</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">Java 基础语法</td>
<td>javaboy4096</td>
</tr>
<tr>
<td style="text-align: left">Java 面向对象</td>
<td>javaboy6148</td>
</tr>
<tr>
<td style="text-align: left">JavaSE 飞机大战项目</td>
<td>javaboy2053</td>
</tr>
<tr>
<td style="text-align: left">深入面向对象和数组</td>
<td>javaboy8200</td>
</tr>
<tr>
<td style="text-align: left">Java 常用类详解</td>
<td>javaboy4105</td>
</tr>
<tr>
<td style="text-align: left">Java 异常机制解析</td>
<td>javaboy6157</td>
</tr>
<tr>
<td style="text-align: left">Java 集合与数据结构</td>
<td>javaboy2062</td>
</tr>
<tr>
<td style="text-align: left">JavaIO 流全解析</td>
<td>javaboy8209</td>
</tr>
<tr>
<td style="text-align: left">深入理解 Java 多线程</td>
<td>javaboy4114</td>
</tr>
<tr>
<td style="text-align: left">Java 网络编程</td>
<td>javaboy6166</td>
</tr>
<tr>
<td style="text-align: left">手动开发一个 Web 服务器</td>
<td>javaboy2071</td>
</tr>
<tr>
<td style="text-align: left">深入理解 Java 注解+反射</td>
<td>javaboy8218</td>
</tr>
<tr>
<td style="text-align: left">Java23 种设计模式</td>
<td>javaboy4123</td>
</tr>
<tr>
<td style="text-align: left">学会 Java 正则表达式</td>
<td>javaboy6175</td>
</tr>
<tr>
<td style="text-align: left">JDBC 详解</td>
<td>javaboy2080</td>
</tr>
<tr>
<td style="text-align: left">独立开发 SORM 框架</td>
<td>javaboy8227</td>
</tr>
<tr>
<td style="text-align: left">快人一步,Java10 新特性全解析</td>
<td>javaboy4132</td>
</tr>
<tr>
<td style="text-align: left">Java 数据结构和算法</td>
<td>javaboy6184</td>
</tr>
<tr>
<td style="text-align: left">深入理解 Java 虚拟机</td>
<td>javaboy2089</td>
</tr>
<tr>
<td style="text-align: left">Java 解析XML文件</td>
<td>javaboy8236</td>
</tr>
</tbody>
</table>
<h2 id="数据库">数据库</h2>
<table>
<thead>
<tr>
<th style="text-align: left">资源名称</th>
<th>口令</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">Oracle 数据库安装及简单 SQL</td>
<td>javaboy4141</td>
</tr>
<tr>
<td style="text-align: left">Oracle 账户管理及查询语句</td>
<td>javaboy6193</td>
</tr>
<tr>
<td style="text-align: left">Oracle 中的函数</td>
<td>javaboy2098</td>
</tr>
<tr>
<td style="text-align: left">Oracle 中的子查询</td>
<td>javaboy8245</td>
</tr>
<tr>
<td style="text-align: left">Oracle 中常见的表操作</td>
<td>javaboy4150</td>
</tr>
<tr>
<td style="text-align: left">Oracle 中的数据备份</td>
<td>javaboy6202</td>
</tr>
<tr>
<td style="text-align: left">MySQL 基础</td>
<td>javaboy2107</td>
</tr>
<tr>
<td style="text-align: left">PowerDesigner 教程</td>
<td>javaboy8254</td>
</tr>
<tr>
<td style="text-align: left">JDBC 操作数据库</td>
<td>javaboy4159</td>
</tr>
<tr>
<td style="text-align: left">MySQL 优化</td>
<td>javaboy6211</td>
</tr>
<tr>
<td style="text-align: left">Oracle 高级课程</td>
<td>javaboy2116</td>
</tr>
<tr>
<td style="text-align: left">数据库与 SQL 优化</td>
<td>javaboy6283</td>
</tr>
<tr>
<td style="text-align: left">数据库集群与高并发</td>
<td>javaboy2188</td>
</tr>
</tbody>
</table>
<h2 id="web-基础">Web 基础</h2>
<table>
<thead>
<tr>
<th style="text-align: left">资源名称</th>
<th>口令</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">HTML 入门教程</td>
<td>javaboy8263</td>
</tr>
<tr>
<td style="text-align: left">CSS 教程</td>
<td>javaboy4168</td>
</tr>
<tr>
<td style="text-align: left">JavaScript 视频教程</td>
<td>javaboy6220</td>
</tr>
<tr>
<td style="text-align: left">jQuery 视频教程</td>
<td>javaboy2125</td>
</tr>
<tr>
<td style="text-align: left">EasyUI 视频教程</td>
<td>javaboy8272</td>
</tr>
<tr>
<td style="text-align: left">Servlet 基础</td>
<td>javaboy4177</td>
</tr>
<tr>
<td style="text-align: left">Servlet 中的 Request 和 Response</td>
<td>javaboy6229</td>
</tr>
<tr>
<td style="text-align: left">Servlet 请求转发与重定向</td>
<td>javaboy2134</td>
</tr>
<tr>
<td style="text-align: left">Session 和 Cookie</td>
<td>javaboy8281</td>
</tr>
<tr>
<td style="text-align: left">JSP 详解</td>
<td>javaboy4186</td>
</tr>
<tr>
<td style="text-align: left">用户管理系统实战</td>
<td>javaboy6238</td>
</tr>
<tr>
<td style="text-align: left">Ajax 详解</td>
<td>javaboy2143</td>
</tr>
<tr>
<td style="text-align: left">EL 和 JSTL</td>
<td>javaboy8290</td>
</tr>
<tr>
<td style="text-align: left">过滤器详解</td>
<td>javaboy4195</td>
</tr>
<tr>
<td style="text-align: left">监听器详解</td>
<td>javaboy6247</td>
</tr>
<tr>
<td style="text-align: left">KnockoutJS 实战视频</td>
<td>javaboy2152</td>
</tr>
</tbody>
</table>
<h2 id="java-高级">Java 高级</h2>
<table>
<thead>
<tr>
<th style="text-align: left">资源名称</th>
<th>口令</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">IntelliJIDEA 视频教程</td>
<td>javaboy4285</td>
</tr>
<tr>
<td style="text-align: left">Java 高并发秒杀方案</td>
<td>javaboy8299</td>
</tr>
<tr>
<td style="text-align: left">Activiti 工作流实战解析</td>
<td>javaboy4204</td>
</tr>
<tr>
<td style="text-align: left">Java 并发编程与高并发实战</td>
<td>javaboy6256</td>
</tr>
<tr>
<td style="text-align: left">Linux 快速入门</td>
<td>javaboy2161</td>
</tr>
<tr>
<td style="text-align: left">Maven 详解</td>
<td>javaboy8308</td>
</tr>
<tr>
<td style="text-align: left">Git 应用详解</td>
<td>javaboy4213</td>
</tr>
<tr>
<td style="text-align: left">Svn 入门教程</td>
<td>javaboy6265</td>
</tr>
<tr>
<td style="text-align: left">高并发编程与线程池</td>
<td>javaboy2170</td>
</tr>
<tr>
<td style="text-align: left">系统优化与 JVM 调优</td>
<td>javaboy8317</td>
</tr>
<tr>
<td style="text-align: left">Java 编程规范</td>
<td>javaboy4222</td>
</tr>
<tr>
<td style="text-align: left">AIO、BIO、NIO 详解</td>
<td>javaboy6274</td>
</tr>
<tr>
<td style="text-align: left">Netty 高级视频教程</td>
<td>javaboy2179</td>
</tr>
<tr>
<td style="text-align: left">ActiveMQ 消息中间详解</td>
<td>javaboy8326</td>
</tr>
<tr>
<td style="text-align: left">单点登录视频教程</td>
<td>javaboy4231</td>
</tr>
<tr>
<td style="text-align: left">Dubbo 详解</td>
<td>javaboy8335</td>
</tr>
<tr>
<td style="text-align: left">Redis 全解析</td>
<td>javaboy4240</td>
</tr>
<tr>
<td style="text-align: left">VSFTPD+NGINX 视频教程</td>
<td>javaboy6292</td>
</tr>
<tr>
<td style="text-align: left">MyBatis 视频教程</td>
<td>javaboy2197</td>
</tr>
<tr>
<td style="text-align: left">Spring4 视频教程</td>
<td>javaboy8344</td>
</tr>
<tr>
<td style="text-align: left">SpringMVC 视频教程</td>
<td>javaboy4249</td>
</tr>
<tr>
<td style="text-align: left">SSM 框架整合视频教程</td>
<td>javaboy6301</td>
</tr>
<tr>
<td style="text-align: left">RBAC 权限控制视频教程</td>
<td>javaboy2206</td>
</tr>
<tr>
<td style="text-align: left">Hibernate4 视频教程</td>
<td>javaboy8353</td>
</tr>
<tr>
<td style="text-align: left">Jfinal 视频教程</td>
<td>javaboy4258</td>
</tr>
<tr>
<td style="text-align: left">Shiro 视频教程</td>
<td>javaboy6310</td>
</tr>
<tr>
<td style="text-align: left">Solr 视频教程</td>
<td>javaboy2215</td>
</tr>
<tr>
<td style="text-align: left">Struts2 视频教程</td>
<td>javaboy8362</td>
</tr>
<tr>
<td style="text-align: left">Nginx 视频教程</td>
<td>javaboy4267</td>
</tr>
<tr>
<td style="text-align: left">Redis 缓存详解</td>
<td>javaboy6319</td>
</tr>
<tr>
<td style="text-align: left">JVM 虚拟机优化</td>
<td>javaboy2224</td>
</tr>
<tr>
<td style="text-align: left">Zookeeper 详解视频</td>
<td>javaboy8371</td>
</tr>
<tr>
<td style="text-align: left">Linux 基本操作</td>
<td>javaboy6328</td>
</tr>
<tr>
<td style="text-align: left">架构师面试攻略(文档)</td>
<td>javaboy2233</td>
</tr>
<tr>
<td style="text-align: left">架构师面试攻略(视频)</td>
<td>javaboy8380</td>
</tr>
<tr>
<td style="text-align: left">JUC 视频教程</td>
<td>javaboy6400</td>
</tr>
<tr>
<td style="text-align: left">MySQL 高级教程</td>
<td>javaboy2305</td>
</tr>
<tr>
<td style="text-align: left">Java 邮件开发教程</td>
<td>javaboy8452</td>
</tr>
<tr>
<td style="text-align: left">Maven 实战视频</td>
<td>javaboy8443</td>
</tr>
<tr>
<td style="text-align: left">自己 DIY 一个 Tomcat</td>
<td>javaboy4339</td>
</tr>
</tbody>
</table>
<h2 id="大前端">大前端</h2>
<table>
<thead>
<tr>
<th style="text-align: left">资源名称</th>
<th>口令</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">HTML5 新特性</td>
<td>javaboy4276</td>
</tr>
<tr>
<td style="text-align: left">AngularJS 视频教程</td>
<td>javaboy6337</td>
</tr>
<tr>
<td style="text-align: left">Grunt 视频教程</td>
<td>javaboy2242</td>
</tr>
<tr>
<td style="text-align: left">Gulp 视频教程</td>
<td>javaboy8389</td>
</tr>
<tr>
<td style="text-align: left">Webpack 视频教程</td>
<td>javaboy4294</td>
</tr>
<tr>
<td style="text-align: left">Bootstrap 视频教程</td>
<td>javaboy6346</td>
</tr>
<tr>
<td style="text-align: left">CSS3 视频教程</td>
<td>javaboy2251</td>
</tr>
<tr>
<td style="text-align: left">ES6 视频教程</td>
<td>javaboy8398</td>
</tr>
<tr>
<td style="text-align: left">HTML5 核心技术</td>
<td>javaboy4303</td>
</tr>
<tr>
<td style="text-align: left">HTML5 实战</td>
<td>javaboy6355</td>
</tr>
<tr>
<td style="text-align: left">HTML5 项目实战</td>
<td>javaboy2260</td>
</tr>
<tr>
<td style="text-align: left">JS 模块化视频教程</td>
<td>javaboy8407</td>
</tr>
<tr>
<td style="text-align: left">less 视频教程</td>
<td>javaboy4312</td>
</tr>
<tr>
<td style="text-align: left">NodeJS 视频教程</td>
<td>javaboy6364</td>
</tr>
<tr>
<td style="text-align: left">React 视频教程</td>
<td>javaboy2269</td>
</tr>
<tr>
<td style="text-align: left">Zepto 视频教程</td>
<td>javaboy8416</td>
</tr>
<tr>
<td style="text-align: left">HTML+CSS 实战视频</td>
<td>javaboy4321</td>
</tr>
<tr>
<td style="text-align: left">JavaScript140 集</td>
<td>javaboy6373</td>
</tr>
<tr>
<td style="text-align: left">jQuery 视频教程</td>
<td>javaboy2278</td>
</tr>
<tr>
<td style="text-align: left">JavaScript 高级语法视频教程</td>
<td>javaboy8425</td>
</tr>
<tr>
<td style="text-align: left">Vue 项目实战视频</td>
<td>javaboy4330</td>
</tr>
<tr>
<td style="text-align: left">CSS3 特效实战</td>
<td>javaboy6382</td>
</tr>
<tr>
<td style="text-align: left">HTML5 特效实战</td>
<td>javaboy2287</td>
</tr>
<tr>
<td style="text-align: left">HTML5+Canvas 实现刮刮卡</td>
<td>javaboy8434</td>
</tr>
<tr>
<td style="text-align: left">Gradle 从入门到精通</td>
<td>javaboy6391</td>
</tr>
<tr>
<td style="text-align: left">mpvue 项目实战</td>
<td>javaboy2296</td>
</tr>
<tr>
<td style="text-align: left">Vue 最新最全视频教程</td>
<td>javaboy4348</td>
</tr>
</tbody>
</table>
<h2 id="大数据">大数据</h2>
<table>
<thead>
<tr>
<th style="text-align: left">资源名称</th>
<th>口令</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">Linux 操作系统</td>
<td>javaboy4357</td>
</tr>
<tr>
<td style="text-align: left">Linux 基本命令</td>
<td>javaboy6409</td>
</tr>
<tr>
<td style="text-align: left">Linux 文件安装</td>
<td>javaboy2314</td>
</tr>
<tr>
<td style="text-align: left">Shell 编程</td>
<td>javaboy8461</td>
</tr>
<tr>
<td style="text-align: left">网络基础知识</td>
<td>javaboy4366</td>
</tr>
<tr>
<td style="text-align: left">LVS 集群与高并发</td>
<td>javaboy6418</td>
</tr>
<tr>
<td style="text-align: left">Nginx 和高并发</td>
<td>javaboy2323</td>
</tr>
<tr>
<td style="text-align: left">keepalive 和单点故障</td>
<td>javaboy8470</td>
</tr>
<tr>
<td style="text-align: left">HDFS 分布式文件系统</td>
<td>javaboy4375</td>
</tr>
<tr>
<td style="text-align: left">mapreduce 分布式计算</td>
<td>javaboy6427</td>
</tr>
<tr>
<td style="text-align: left">YARN 资源管理与任务调度</td>
<td>javaboy2332</td>
</tr>
<tr>
<td style="text-align: left">mapreduce 计算案例</td>
<td>javaboy8479</td>
</tr>
<tr>
<td style="text-align: left">HIVE 视频教程</td>
<td>javaboy4384</td>
</tr>
<tr>
<td style="text-align: left">Hbase 数据库详解</td>
<td>javaboy6436</td>
</tr>
<tr>
<td style="text-align: left">zookeeper 协同处理</td>
<td>javaboy2341</td>
</tr>
<tr>
<td style="text-align: left">CDH 使用</td>
<td>javaboy8488</td>
</tr>
<tr>
<td style="text-align: left">HUE 使用</td>
<td>javaboy4393</td>
</tr>
<tr>
<td style="text-align: left">IMPALA 详解</td>
<td>javaboy6445</td>
</tr>
<tr>
<td style="text-align: left">oozie 详解</td>
<td>javaboy2350</td>
</tr>
<tr>
<td style="text-align: left">elasticsearch 详解</td>
<td>javaboy8497</td>
</tr>
<tr>
<td style="text-align: left">Redis 内存数据</td>
<td>javaboy4402</td>
</tr>
<tr>
<td style="text-align: left">Scala 入门</td>
<td>javaboy6454</td>
</tr>
<tr>
<td style="text-align: left">Spark 详解</td>
<td>javaboy2359</td>
</tr>
<tr>
<td style="text-align: left">Spark 高级</td>
<td>javaboy8506</td>
</tr>
<tr>
<td style="text-align: left">Spark-Stream 流式计算</td>
<td>javaboy4411</td>
</tr>
<tr>
<td style="text-align: left">Kafka 分布式消息队列</td>
<td>javaboy6463</td>
</tr>
<tr>
<td style="text-align: left">STORM 流式计算框架</td>
<td>javaboy2368</td>
</tr>
<tr>
<td style="text-align: left">Python 语言基础</td>
<td>javaboy8515</td>
</tr>
<tr>
<td style="text-align: left">回归算法</td>
<td>javaboy4420</td>
</tr>
<tr>
<td style="text-align: left">分类算法、决策树</td>
<td>javaboy6472</td>
</tr>
<tr>
<td style="text-align: left">聚类算法、微博案例</td>
<td>javaboy2377</td>
</tr>
<tr>
<td style="text-align: left">推荐算法</td>
<td>javaboy8524</td>
</tr>
<tr>
<td style="text-align: left">大型电商日志分析(项目实战)</td>
<td>javaboy4429</td>
</tr>
<tr>
<td style="text-align: left">智慧交通(项目实战)</td>
<td>javaboy6481</td>
</tr>
<tr>
<td style="text-align: left">智能 App(项目实战)</td>
<td>javaboy2386</td>
</tr>
</tbody>
</table>
<h2 id="人工智能">人工智能</h2>
<table>
<thead>
<tr>
<th style="text-align: left">资源名称</th>
<th>口令</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">人工智能入门</td>
<td>javaboy8533</td>
</tr>
<tr>
<td style="text-align: left">线性回归深入与代码实现</td>
<td>javaboy4438</td>
</tr>
<tr>
<td style="text-align: left">梯度下降算发实现</td>
<td>javaboy6490</td>
</tr>
<tr>
<td style="text-align: left">逻辑回归详解和应用</td>
<td>javaboy2395</td>
</tr>
<tr>
<td style="text-align: left">分类项目案例与神经网络算法</td>
<td>javaboy8542</td>
</tr>
<tr>
<td style="text-align: left">多分类、决策树分类与随机森林分类</td>
<td>javaboy4447</td>
</tr>
<tr>
<td style="text-align: left">分类评估与聚类</td>
<td>javaboy6499</td>
</tr>
<tr>
<td style="text-align: left">密度聚类与谱聚类</td>
<td>javaboy2404</td>
</tr>
<tr>
<td style="text-align: left">Tensorflow 安装并实现线性回归</td>
<td>javaboy8551</td>
</tr>
<tr>
<td style="text-align: left">TensorFlow 深入、TensorFlow可视化</td>
<td>javaboy4456</td>
</tr>
<tr>
<td style="text-align: left">DNN 深度神经网络手写图片识别</td>
<td>javaboy6508</td>
</tr>
<tr>
<td style="text-align: left">TensorBoard 可视化</td>
<td>javaboy2413</td>
</tr>
<tr>
<td style="text-align: left">卷积神经网络、CNN 识别图片</td>
<td>javaboy8560</td>
</tr>
<tr>
<td style="text-align: left">卷积神经网络深入,AlexNet 模型实现</td>
<td>javaboy4465</td>
</tr>
<tr>
<td style="text-align: left">Keras 深度学习框架</td>
<td>javaboy6517</td>
</tr>
</tbody>
</table>
<h2 id="分布式相关">分布式相关</h2>
<table>
<thead>
<tr>
<th style="text-align: left">资源名称</th>
<th>口令</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">ZooKeeper 简介</td>
<td>javaboy2422</td>
</tr>
<tr>
<td style="text-align: left">ZooKeeper 安装</td>
<td>javaboy8569</td>
</tr>
<tr>
<td style="text-align: left">ZooKeeper 基本数据模型</td>
<td>javaboy4474</td>
</tr>
<tr>
<td style="text-align: left">基于 Linux 的 ZK 客户端命令</td>
<td>javaboy6526</td>
</tr>
<tr>
<td style="text-align: left">选举模式和 ZK 集群安装</td>
<td>javaboy2431</td>
</tr>
<tr>
<td style="text-align: left">JavaAPI 操作 ZK</td>
<td>javaboy8578</td>
</tr>
<tr>
<td style="text-align: left">ApacheCurator 客户端</td>
<td>javaboy4483</td>
</tr>
<tr>
<td style="text-align: left">Dubbo 入门到重构服务</td>
<td>javaboy6535</td>
</tr>
<tr>
<td style="text-align: left">分布式锁</td>
<td>javaboy2440</td>
</tr>
<tr>
<td style="text-align: left">Zookeeper 总结</td>
<td>javaboy8587</td>
</tr>
</tbody>
</table>
<h2 id="项目实战">项目实战</h2>
<table>
<thead>
<tr>
<th style="text-align: left">资源名称</th>
<th>口令</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">OA 办公自动化项目1</td>
<td>javaboy4492</td>
</tr>
<tr>
<td style="text-align: left">OA 办公自动化项目2</td>
<td>javaboy6544</td>
</tr>
<tr>
<td style="text-align: left">OA 办公自动化项目3</td>
<td>javaboy2449</td>
</tr>
<tr>
<td style="text-align: left">OA 办公自动化项目4</td>
<td>javaboy8596</td>
</tr>
<tr>
<td style="text-align: left">备锋客户关系管理(CRM)系统</td>
<td>javaboy4501</td>
</tr>
<tr>
<td style="text-align: left">百战客户关系管理系统</td>
<td>javaboy6553</td>
</tr>
<tr>
<td style="text-align: left">宅急送项目</td>
<td>javaboy2458</td>
</tr>
<tr>
<td style="text-align: left">高仿人人网项目</td>
<td>javaboy8605</td>
</tr>
<tr>
<td style="text-align: left">Java 邮件开发项目</td>
<td>javaboy4510</td>
</tr>
<tr>
<td style="text-align: left">在线支付实战视频</td>
<td>javaboy6562</td>
</tr>
<tr>
<td style="text-align: left">俄罗斯方块游戏实战</td>
<td>javaboy2467</td>
</tr>
<tr>
<td style="text-align: left">贪吃蛇视频教程</td>
<td>javaboy8614</td>
</tr>
<tr>
<td style="text-align: left">交通灯管理系统</td>
<td>javaboy4519</td>
</tr>
<tr>
<td style="text-align: left">银行业务调度系统实战</td>
<td>javaboy6571</td>
</tr>
<tr>
<td style="text-align: left">供应链系统实战视频</td>
<td>javaboy2476</td>
</tr>
<tr>
<td style="text-align: left">网上商城项目实战</td>
<td>javaboy8623</td>
</tr>
<tr>
<td style="text-align: left">医药采购平台管理系统</td>
<td>javaboy4528</td>
</tr>
<tr>
<td style="text-align: left">点餐系统实战</td>
<td>javaboy6580</td>
</tr>
<tr>
<td style="text-align: left">杰信商贸 SSM 版</td>
<td>javaboy2485</td>
</tr>
<tr>
<td style="text-align: left">国家税务协同平台项目</td>
<td>javaboy8632</td>
</tr>
<tr>
<td style="text-align: left">javaWeb 聊天室</td>
<td>javaboy4537</td>
</tr>
<tr>
<td style="text-align: left">网上书店</td>
<td>javaboy6589</td>
</tr>
<tr>
<td style="text-align: left">手机进销存系统</td>
<td>javaboy2494</td>
</tr>
<tr>
<td style="text-align: left">QQ 聊天器</td>
<td>javaboy8641</td>
</tr>
<tr>
<td style="text-align: left">ERP 项目</td>
<td>javaboy4546</td>
</tr>
<tr>
<td style="text-align: left">坦克大战</td>
<td>javaboy6598</td>
</tr>
<tr>
<td style="text-align: left">五子棋游戏</td>
<td>javaboy2503</td>
</tr>
<tr>
<td style="text-align: left">报名系统 Activity</td>
<td>javaboy8650</td>
</tr>
<tr>
<td style="text-align: left">OA 供应链系统</td>
<td>javaboy4555</td>
</tr>
<tr>
<td style="text-align: left">用户管理系统</td>
<td>javaboy6607</td>
</tr>
<tr>
<td style="text-align: left">JavaWeb 图书商城</td>
<td>javaboy2512</td>
</tr>
<tr>
<td style="text-align: left">VIP 商场</td>
<td>javaboy8659</td>
</tr>
<tr>
<td style="text-align: left">企业招聘系统</td>
<td>javaboy4564</td>
</tr>
<tr>
<td style="text-align: left">博客系统项目</td>
<td>javaboy6616</td>
</tr>
<tr>
<td style="text-align: left">超级玛丽</td>
<td>javaboy2521</td>
</tr>
<tr>
<td style="text-align: left">成绩管理系统</td>
<td>javaboy8668</td>
</tr>
<tr>
<td style="text-align: left">个人理财系统</td>
<td>javaboy4573</td>
</tr>
<tr>
<td style="text-align: left">人事管理系统</td>
<td>javaboy6625</td>
</tr>
<tr>
<td style="text-align: left">JBPM 采购申请系统</td>
<td>javaboy2530</td>
</tr>
<tr>
<td style="text-align: left">电子商务网站</td>
<td>javaboy8677</td>
</tr>
<tr>
<td style="text-align: left">跨平台 App 开发</td>
<td>javaboy4582</td>
</tr>
</tbody>
</table>
<h2 id="文档资源">文档资源</h2>
<table>
<thead>
<tr>
<th style="text-align: left">资源名称</th>
<th>口令</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">Docker 教程</td>
<td>docker</td>
</tr>
<tr>
<td style="text-align: left">Redis 教程</td>
<td>redis</td>
</tr>
<tr>
<td style="text-align: left">RocketMQ 教程</td>
<td>rocketmq</td>
</tr>
<tr>
<td style="text-align: left">Java8 新特性文档</td>
<td>java8</td>
</tr>
<tr>
<td style="text-align: left">设计模式教程</td>
<td>设计模式</td>
</tr>
<tr>
<td style="text-align: left">网络协议教程</td>
<td>网络</td>
</tr>
<tr>
<td style="text-align: left">netty 教程</td>
<td>netty</td>
</tr>
<tr>
<td style="text-align: left">web 全栈指南</td>
<td>web全栈</td>
</tr>
</tbody>
</table>
<p><strong>好了,本次就先更新这么多,如果这里没有你想要的,也也可以留言说说你需要的资料,松哥会及时更新资源哦。</strong> 另外,大家在公众号后台回复 <strong>资源</strong> ,也可以获取本文电子版。</p>
<p><img src="http://www.javaboy.org/images/other/86-1.png" alt="" /></p>
<p>如果这些资源帮助到你了,欢迎转发给更多小伙伴哦。</p>江南一点雨wangsong0210@gmail.com今年 5 月份的时候,松哥发了一个视频资源库,当时和大家说,这个资源库会定期更新,后来却迟迟未更新,其实不是我没资源了,是因为当时的关键字是我一个一个在微信后台配置的,配置到后面发现,后台配置关键字有数量上限,没法继续配置了,所以这事就搁置下来了。还在用 Dockerfile 部署 Spring Boot?out 啦!试试谷歌的大杀器 Jib2019-11-07T10:33:35+00:002019-11-07T10:33:35+00:00http://springboot.javaboy.org/2019/1107/springboot-docker<p>之前松哥和大家分享过一篇将 Spring Boot 项目部署到远程 Docker 上的文章:</p>
<!--more-->
<ul>
<li><a href="https://mp.weixin.qq.com/s/vSCQLvQBYMYoPhdlO2v3XA">一键部署 Spring Boot 到远程 Docker 容器</a></li>
</ul>
<p>但是这种部署有一个问题,就是一个小小的 helloworld 构建成镜像之后,竟然都有 660 MB+,这就有点过分了;而且这种方式步骤繁琐,很多人看了头大。</p>
<p>因此松哥今天想再和大家聊一聊另外一种方案 <strong>Jib</strong>,这是谷歌开源的一个容器化运行方案,使用它我们将 Spring Boot 进行容器化部署只要两步:</p>
<ul>
<li>第一步配置 Maven Plugin</li>
<li>第二步构建</li>
</ul>
<p>我们一起来看看。</p>
<h2 id="jib">Jib</h2>
<p>在之前那篇文章中,我们将 Spring Boot 项目进行容器化部署,要求开发人员要有一定的 Docker 技能作为支撑,然而在实际开发中,并非每个人都是 Docker 专家,或者说会用 Docker。</p>
<p>有鉴于此,Google 搞出来一个 Jib,使 Spring Boot 容器化部署变得更加简便,开发人员可以不需要任何 Docker 相关的技能,就能将 Spring Boot 项目构建成 Docker 中的镜像,而且还可以“顺便”将镜像 push 到 register 上,极大的简化了部署过程。</p>
<p>Jib 使用 Java 开发,使用也非常简单,可以作为 Maven 或者 Gradle 的插件直接集成到我们的项目中。它利用镜像分层和注册表缓存来实现快速、增量的构建。Jib 会自动读取项目的构建配置,代码组织到不同的层(依赖项、资源、类)中,然后它只会重新构建和推送发生变更的层。在项目进行快速迭代时,Jib 只将发生变更的层推送到 registers 来缩短构建时间。</p>
<p>好了,大致了解了 Jib 之后,接下来我们来看看 Jib 要怎么使用。</p>
<h2 id="准备工作">准备工作</h2>
<p>Jib 可以直接将构建好的镜像 push 到 registers 上,如果公司有自己的私有镜像站的话,可以直接推送到私有镜像站上,本文我就将构建好的镜像推送到官方的 Docker Hub 上,因此需要大家提前准备一个 Docker Hub 的账号,账号大家可以直接去 Docker Hub 上面注册(<a href="https://hub.docker.com/">https://hub.docker.com/</a>),大家要是对 Docker Hub 这些东西不了解,可以在公众号后台回复 docker,获取松哥自制的 Docker 教程。</p>
<h2 id="牛刀小试">牛刀小试</h2>
<p>首先我们来创建一个 Spring Boot 工程,创建时只需要添加一个 Web 依赖即可:</p>
<p><img src="http://www.javaboy.org/images/boot/46-1.png" alt="" /></p>
<p>项目创建成功后,添加一个 HelloController 用来做测试:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestController</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">HelloController</span> <span class="o">{</span>
<span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/hello"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">hello</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="s">"hello jib"</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>然后,在 pom.xml 中添加上 Jib 的插件,如下:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><plugin></span>
<span class="nt"><groupId></span>com.google.cloud.tools<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>jib-maven-plugin<span class="nt"></artifactId></span>
<span class="nt"><version></span>1.7.0<span class="nt"></version></span>
<span class="nt"><configuration></span>
<span class="nt"><from></span>
<span class="nt"><image></span>openjdk:alpine<span class="nt"></image></span>
<span class="nt"></from></span>
<span class="nt"><to></span>
<span class="nt"><image></span>docker.io/wongsung/dockerjib<span class="nt"></image></span>
<span class="nt"><tags></span>
<span class="nt"><tag></span>v1<span class="nt"></tag></span>
<span class="nt"></tags></span>
<span class="nt"><auth></span>
<span class="nt"><username></span>wongsung<span class="nt"></username></span>
<span class="nt"><password></span>你的密码<span class="nt"></password></span>
<span class="nt"></auth></span>
<span class="nt"></to></span>
<span class="nt"></configuration></span>
<span class="nt"><executions></span>
<span class="nt"><execution></span>
<span class="nt"><phase></span>package<span class="nt"></phase></span>
<span class="nt"><goals></span>
<span class="nt"><goal></span>build<span class="nt"></goal></span>
<span class="nt"></goals></span>
<span class="nt"></execution></span>
<span class="nt"></executions></span>
<span class="nt"></plugin></span>
</code></pre></div></div>
<p>关于这段配置,我说如下几点:</p>
<ol>
<li>首先就是版本号的问题,我这里使用的是 <code class="language-plaintext highlighter-rouge">1.7.0</code> ,网上有的教程比较老,用的 0.x 的版本,老的版本在配置 Docker 认证的时候非常麻烦,所以版本这块建议大家使用当前最新版。</li>
<li>from 中的配置表示本镜像构建所基于的根镜像为 <code class="language-plaintext highlighter-rouge">openjdk:alpine</code></li>
<li>to 中的配置表示本镜像构建完成后,要发布到哪里去,如果是发布到私有镜像站,就写自己私有镜像站的地址,如果是发布到 Docker Hub 上,就参考我这里的写法 <code class="language-plaintext highlighter-rouge">docker.io/wongsung/dockerjib</code>,其中 wongsung 表示你在 Docker Hub 上注册的用户名,dockerjib 表示你镜像的名字,可以随意取。</li>
<li>tags 中配置的是自己镜像的版本。</li>
<li>auth 中配置你在 Docker Hub 上的用户名/密码。</li>
<li>executions 节点中的就是常规配置了,我就不再多说了。</li>
</ol>
<p>配置完成后,在命令行执行如下命令将当前下项目构建成一个 Docker 镜像并 push 到 Docker Hub:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn compile jib:build
</code></pre></div></div>
<p>构建完成后,我们在 Docker Hub 上就能看到自己的镜像了:</p>
<p><img src="http://www.javaboy.org/images/boot/46-2.png" alt="" /></p>
<p>接下来,启动 Docker ,在 Docker 中执行如下命令拉取镜像下来并运行:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -d --name mydockerjib -p 8080:8080 docker.io/wongsung/dockerjib:v1
</code></pre></div></div>
<p>启动成功后,我们在浏览器中就可以直接访问我们刚才的 Spring Boot 项目中的 hello 接口了:</p>
<p><img src="http://www.javaboy.org/images/boot/46-3.png" alt="" /></p>
<p>是不是很方便?比我第一次给大家介绍的方案要方便很多。</p>
<p><strong>注意</strong></p>
<p>这种方式是将项目构建成镜像后并 push 到 registers 上,这种构建方式不需要你本地安装 Docker,如果你需要在本地运行镜像,那当然需要 Docker,单纯的构建是不需要 Docker 环境的。</p>
<h2 id="本地构建">本地构建</h2>
<p>如果你电脑本地刚好安装了 Docker ,有 Docker 环境,那么也可以将项目构建成本地 Docker 的镜像,</p>
<p>首先我们来查看一下本地镜像:</p>
<p><img src="http://www.javaboy.org/images/boot/46-4.png" alt="" /></p>
<p>可以看到只有 MySQL 镜像,然后我们执行如下命令构建本地镜像:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn compile jib:dockerBuild
</code></pre></div></div>
<p>看到如下构建日志信息表示构建成功:</p>
<p><img src="http://www.javaboy.org/images/boot/46-5.png" alt="" /></p>
<p>构建完成后,我们再来看本地镜像:</p>
<p><img src="http://www.javaboy.org/images/boot/46-6.png" alt="" /></p>
<p>可以都看到,已经构建成功了,接下来启动命令和上面一样,我就不重复展示了。</p>
<h2 id="结语">结语</h2>
<p>容器的出现,让我们的 Java 程序比任何时候都接近“一次编写,到处运行”,Spring Boot 容器化部署也是越来越方便,后面有空松哥再和大家聊聊结合 jenkins 的用法,好了,本文的案例我已经上传到 GitHub:https://github.com/lenve/javaboy-code-samples,有问题欢迎留言讨论。</p>江南一点雨wangsong0210@gmail.com之前松哥和大家分享过一篇将 Spring Boot 项目部署到远程 Docker 上的文章:Spring Boot 开发微信公众号后台(二)2019-10-31T10:32:24+00:002019-10-31T10:32:24+00:00http://springboot.javaboy.org/2019/1031/springboot-weixin<p>hello 各位小伙伴,今天我们来继续学习如何通过 Spring Boot 开发微信公众号。还没阅读过上篇文章的小伙伴建议先看看上文,有助于理解本文:</p>
<!--more-->
<ul>
<li><a href="https://mp.weixin.qq.com/s/f3QexxLp9vT6aE1Pl3jHGw">Spring Boot 开发微信公众号后台</a></li>
</ul>
<p>上篇文章中我们将微信服务器和我们自己的服务器对接起来了,并且在自己的服务器上也能收到微信服务器发来的消息,本文我们要看的就是如何给微信服务器回复消息。</p>
<h2 id="消息分类">消息分类</h2>
<p>在讨论如何给微信服务器回复消息之前,我们需要先来了解下微信服务器发来的消息主要有哪些类型以及我们回复给微信的消息都有哪些类型。</p>
<p>在上文中大家了解到,微信发送来的 xml 消息中有一个 MsgType 字段,这个字段就是用来标记消息的类型。这个类型可以标记出这条消息是普通消息还是事件消息还是图文消息等。</p>
<p>普通消息主要是指:</p>
<ul>
<li>文本消息</li>
<li>图片消息</li>
<li>语音消息</li>
<li>视频消息</li>
<li>小视频消息</li>
<li>地址位置消息</li>
<li>链接消息</li>
</ul>
<p>不同的消息类型,对应不同的 MsgType,这里我还是以普通消息为例,如下:</p>
<table>
<thead>
<tr>
<th style="text-align: left">消息类型</th>
<th style="text-align: left">MsgType</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">文本消息</td>
<td style="text-align: left">text</td>
</tr>
<tr>
<td style="text-align: left">图片消息</td>
<td style="text-align: left">image</td>
</tr>
<tr>
<td style="text-align: left">语音消息</td>
<td style="text-align: left">voice</td>
</tr>
<tr>
<td style="text-align: left">视频消息</td>
<td style="text-align: left">video</td>
</tr>
<tr>
<td style="text-align: left">小视频消息</td>
<td style="text-align: left">shortvideo</td>
</tr>
<tr>
<td style="text-align: left">地址位置消息</td>
<td style="text-align: left">location</td>
</tr>
<tr>
<td style="text-align: left">链接消息</td>
<td style="text-align: left">link</td>
</tr>
</tbody>
</table>
<p>大家千万不要以为不同类型消息的格式是一样的,其实是不一样的,也就是说,MsgType 为 text 的消息和 MsgType 为 image 的消息,微信服务器发给我们的消息内容是不一样的,这样带来一个问题就是我无法使用一个 Bean 去接收不同类型的数据,因此这里我们一般使用 Map 接收即可。</p>
<p>这是消息的接收,除了消息的接收之外,还有一个消息的回复,我们回复的消息也有很多类型,可以回复普通消息,也可以回复图片消息,回复语音消息等,不同的回复消息我们可以进行相应的封装。因为不同的返回消息实例也是有一些共同的属性的,例如消息是谁发来的,发给谁,消息类型,消息 id 等,所以我们可以将这些共同的属性定义成一个父类,然后不同的消息再去继承这个父类。</p>
<h2 id="返回消息类型定义">返回消息类型定义</h2>
<p>首先我们来定义一个公共的消息类型:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">BaseMessage</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="nc">ToUserName</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="nc">FromUserName</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">long</span> <span class="nc">CreateTime</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="nc">MsgType</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">long</span> <span class="nc">MsgId</span><span class="o">;</span>
<span class="c1">//省略 getter/setter</span>
<span class="o">}</span>
</code></pre></div></div>
<p>在这里:</p>
<ul>
<li>ToUserName 表示开发者的微信号</li>
<li>FromUserName 表示发送方账号(用户的 OpenID)</li>
<li>CreateTime 消息的创建时间</li>
<li>MsgType 表示消息的类型</li>
<li>MsgId 表示消息 id</li>
</ul>
<p>这是我们的基本消息类型,就是说,我们返回给用户的消息,无论是什么类型的消息,都有这几个基本属性。然后在此基础上,我们再去扩展出文本消息、图片消息 等。</p>
<p>我们来看下文本消息的定义:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">TextMessage</span> <span class="kd">extends</span> <span class="nc">BaseMessage</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="nc">Content</span><span class="o">;</span>
<span class="c1">//省略 getter/setter</span>
<span class="o">}</span>
</code></pre></div></div>
<p>文本消息在前面消息的基础上多了一个 Content 属性,因此文本消息继承自 BaseMessage ,再额外添加一个 Content 属性即可。</p>
<p>其他的消息类型也是类似的定义,我就不一一列举了,至于其他消息的格式,大家可以参考微信开放文档(http://1t.click/aPXK)。</p>
<h2 id="返回消息生成">返回消息生成</h2>
<p>消息类型的 Bean 定义完成之后,接下来就是将实体类生成 XML。</p>
<p>首先我们定义一个消息工具类,将常见的消息类型枚举出来:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* 返回消息类型:文本
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">RESP_MESSAGE_TYPE_TEXT</span> <span class="o">=</span> <span class="s">"text"</span><span class="o">;</span>
<span class="cm">/**
* 返回消息类型:音乐
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">RESP_MESSAGE_TYPE_MUSIC</span> <span class="o">=</span> <span class="s">"music"</span><span class="o">;</span>
<span class="cm">/**
* 返回消息类型:图文
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">RESP_MESSAGE_TYPE_NEWS</span> <span class="o">=</span> <span class="s">"news"</span><span class="o">;</span>
<span class="cm">/**
* 返回消息类型:图片
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">RESP_MESSAGE_TYPE_Image</span> <span class="o">=</span> <span class="s">"image"</span><span class="o">;</span>
<span class="cm">/**
* 返回消息类型:语音
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">RESP_MESSAGE_TYPE_Voice</span> <span class="o">=</span> <span class="s">"voice"</span><span class="o">;</span>
<span class="cm">/**
* 返回消息类型:视频
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">RESP_MESSAGE_TYPE_Video</span> <span class="o">=</span> <span class="s">"video"</span><span class="o">;</span>
<span class="cm">/**
* 请求消息类型:文本
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">REQ_MESSAGE_TYPE_TEXT</span> <span class="o">=</span> <span class="s">"text"</span><span class="o">;</span>
<span class="cm">/**
* 请求消息类型:图片
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">REQ_MESSAGE_TYPE_IMAGE</span> <span class="o">=</span> <span class="s">"image"</span><span class="o">;</span>
<span class="cm">/**
* 请求消息类型:链接
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">REQ_MESSAGE_TYPE_LINK</span> <span class="o">=</span> <span class="s">"link"</span><span class="o">;</span>
<span class="cm">/**
* 请求消息类型:地理位置
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">REQ_MESSAGE_TYPE_LOCATION</span> <span class="o">=</span> <span class="s">"location"</span><span class="o">;</span>
<span class="cm">/**
* 请求消息类型:音频
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">REQ_MESSAGE_TYPE_VOICE</span> <span class="o">=</span> <span class="s">"voice"</span><span class="o">;</span>
<span class="cm">/**
* 请求消息类型:视频
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">REQ_MESSAGE_TYPE_VIDEO</span> <span class="o">=</span> <span class="s">"video"</span><span class="o">;</span>
<span class="cm">/**
* 请求消息类型:推送
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">REQ_MESSAGE_TYPE_EVENT</span> <span class="o">=</span> <span class="s">"event"</span><span class="o">;</span>
<span class="cm">/**
* 事件类型:subscribe(订阅)
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">EVENT_TYPE_SUBSCRIBE</span> <span class="o">=</span> <span class="s">"subscribe"</span><span class="o">;</span>
<span class="cm">/**
* 事件类型:unsubscribe(取消订阅)
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">EVENT_TYPE_UNSUBSCRIBE</span> <span class="o">=</span> <span class="s">"unsubscribe"</span><span class="o">;</span>
<span class="cm">/**
* 事件类型:CLICK(自定义菜单点击事件)
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">EVENT_TYPE_CLICK</span> <span class="o">=</span> <span class="s">"CLICK"</span><span class="o">;</span>
<span class="cm">/**
* 事件类型:VIEW(自定义菜单 URl 视图)
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">EVENT_TYPE_VIEW</span> <span class="o">=</span> <span class="s">"VIEW"</span><span class="o">;</span>
<span class="cm">/**
* 事件类型:LOCATION(上报地理位置事件)
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">EVENT_TYPE_LOCATION</span> <span class="o">=</span> <span class="s">"LOCATION"</span><span class="o">;</span>
<span class="cm">/**
* 事件类型:LOCATION(上报地理位置事件)
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">EVENT_TYPE_SCAN</span> <span class="o">=</span> <span class="s">"SCAN"</span><span class="o">;</span>
</code></pre></div></div>
<p>大家注意这里消息类型的定义,以 RESP 开头的表示返回的消息类型,以 REQ 表示微信服务器发来的消息类型。然后在这个工具类中再定义两个方法,用来将返回的对象转换成 XML:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">textMessageToXml</span><span class="o">(</span><span class="nc">TextMessage</span> <span class="n">textMessage</span><span class="o">)</span> <span class="o">{</span>
<span class="n">xstream</span><span class="o">.</span><span class="na">alias</span><span class="o">(</span><span class="s">"xml"</span><span class="o">,</span> <span class="n">textMessage</span><span class="o">.</span><span class="na">getClass</span><span class="o">());</span>
<span class="k">return</span> <span class="n">xstream</span><span class="o">.</span><span class="na">toXML</span><span class="o">(</span><span class="n">textMessage</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">XStream</span> <span class="n">xstream</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">XStream</span><span class="o">(</span><span class="k">new</span> <span class="nc">XppDriver</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">public</span> <span class="nc">HierarchicalStreamWriter</span> <span class="nf">createWriter</span><span class="o">(</span><span class="nc">Writer</span> <span class="n">out</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">PrettyPrintWriter</span><span class="o">(</span><span class="n">out</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">boolean</span> <span class="n">cdata</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="nd">@SuppressWarnings</span><span class="o">(</span><span class="s">"rawtypes"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">startNode</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nc">Class</span> <span class="n">clazz</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">startNode</span><span class="o">(</span><span class="n">name</span><span class="o">,</span> <span class="n">clazz</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">writeText</span><span class="o">(</span><span class="nc">QuickWriter</span> <span class="n">writer</span><span class="o">,</span> <span class="nc">String</span> <span class="n">text</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">cdata</span><span class="o">)</span> <span class="o">{</span>
<span class="n">writer</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="s">"<![CDATA["</span><span class="o">);</span>
<span class="n">writer</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">text</span><span class="o">);</span>
<span class="n">writer</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="s">"]]>"</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">writer</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">text</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">};</span>
<span class="o">}</span>
<span class="o">});</span>
</code></pre></div></div>
<p>textMessageToXML 方法用来将 TextMessage 对象转成 XML 返回给微信服务器,类似的方法我们还需要定义 imageMessageToXml、voiceMessageToXml 等,不过定义的方式都基本类似,我就不一一列出来了。</p>
<h2 id="返回消息分发">返回消息分发</h2>
<p>由于用户发来的消息可能存在多种情况,我们需要分类进行处理,这个就涉及到返回消息的分发问题。因此我在这里再定义一个返回消息分发的工具类,如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MessageDispatcher</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">processMessage</span><span class="o">(</span><span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">></span> <span class="n">map</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">openid</span> <span class="o">=</span> <span class="n">map</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"FromUserName"</span><span class="o">);</span> <span class="c1">//用户 openid</span>
<span class="nc">String</span> <span class="n">mpid</span> <span class="o">=</span> <span class="n">map</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"ToUserName"</span><span class="o">);</span> <span class="c1">//公众号原始 ID</span>
<span class="k">if</span> <span class="o">(</span><span class="n">map</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"MsgType"</span><span class="o">).</span><span class="na">equals</span><span class="o">(</span><span class="nc">MessageUtil</span><span class="o">.</span><span class="na">REQ_MESSAGE_TYPE_TEXT</span><span class="o">))</span> <span class="o">{</span>
<span class="c1">//普通文本消息</span>
<span class="nc">TextMessage</span> <span class="n">txtmsg</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">TextMessage</span><span class="o">();</span>
<span class="n">txtmsg</span><span class="o">.</span><span class="na">setToUserName</span><span class="o">(</span><span class="n">openid</span><span class="o">);</span>
<span class="n">txtmsg</span><span class="o">.</span><span class="na">setFromUserName</span><span class="o">(</span><span class="n">mpid</span><span class="o">);</span>
<span class="n">txtmsg</span><span class="o">.</span><span class="na">setCreateTime</span><span class="o">(</span><span class="k">new</span> <span class="nc">Date</span><span class="o">().</span><span class="na">getTime</span><span class="o">());</span>
<span class="n">txtmsg</span><span class="o">.</span><span class="na">setMsgType</span><span class="o">(</span><span class="nc">MessageUtil</span><span class="o">.</span><span class="na">RESP_MESSAGE_TYPE_TEXT</span><span class="o">);</span>
<span class="n">txtmsg</span><span class="o">.</span><span class="na">setContent</span><span class="o">(</span><span class="s">"这是返回消息"</span><span class="o">);</span>
<span class="k">return</span> <span class="nc">MessageUtil</span><span class="o">.</span><span class="na">textMessageToXml</span><span class="o">(</span><span class="n">txtmsg</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">processEvent</span><span class="o">(</span><span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">></span> <span class="n">map</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//在这里处理事件</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>这里我们还可以多加几个 elseif 去判断不同的消息类型,我这里因为只有普通文本消息,所以一个 if 就够用了。</p>
<p>在这里返回值我写死了,实际上这里需要根据微信服务端传来的 Content 去数据中查询,将查询结果返回,数据库查询这一套相信大家都能搞定,我这里就不重复介绍了。</p>
<p>最后在消息接收 Controller 中调用该方法,如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@PostMapping</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/verify_wx_token"</span><span class="o">,</span><span class="n">produces</span> <span class="o">=</span> <span class="s">"application/xml;charset=utf-8"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">handler</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">response</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">request</span><span class="o">.</span><span class="na">setCharacterEncoding</span><span class="o">(</span><span class="s">"UTF-8"</span><span class="o">);</span>
<span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">></span> <span class="n">map</span> <span class="o">=</span> <span class="nc">MessageUtil</span><span class="o">.</span><span class="na">parseXml</span><span class="o">(</span><span class="n">request</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">msgType</span> <span class="o">=</span> <span class="n">map</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"MsgType"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="nc">MessageUtil</span><span class="o">.</span><span class="na">REQ_MESSAGE_TYPE_EVENT</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">msgType</span><span class="o">))</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">messageDispatcher</span><span class="o">.</span><span class="na">processEvent</span><span class="o">(</span><span class="n">map</span><span class="o">);</span>
<span class="o">}</span><span class="k">else</span><span class="o">{</span>
<span class="k">return</span> <span class="n">messageDispatcher</span><span class="o">.</span><span class="na">processMessage</span><span class="o">(</span><span class="n">map</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>在 Controller 中,我们首先判断消息是否是事件,如果是事件,进入到事件处理通道,如果不是事件,则进入到消息处理通道。</p>
<p><strong>注意,这里需要配置一下返回消息的编码,否则可能会出现中文乱码。</strong></p>
<p>如此之后,我们的服务器就可以给公众号返回消息了。</p>
<p>上篇文章发出后,有小伙伴问松哥这个会不会开源,我可以负责任的告诉大家,肯定会开源,这个系列截稿后,我把代码处理下就上传到 GitHub。</p>
<p>好了,本文我们就先说到这里。</p>江南一点雨wangsong0210@gmail.comhello 各位小伙伴,今天我们来继续学习如何通过 Spring Boot 开发微信公众号。还没阅读过上篇文章的小伙伴建议先看看上文,有助于理解本文:Spring Boot 开发微信公众号后台2019-10-29T10:32:07+00:002019-10-29T10:32:07+00:00http://springboot.javaboy.org/2019/1029/springboot-weixin<p>Hello 各位小伙伴,松哥今天要和大家聊一个有意思的话题,就是使用 Spring Boot 开发微信公众号后台。</p>
<!--more-->
<p>很多小伙伴可能注意到松哥的个人网站(http://www.javaboy.org)前一阵子上线了一个公众号内回复口令解锁网站文章的功能,还有之前就有的公众号内回复口令获取超 2TB 免费视频教程的功能(<a href="https://mp.weixin.qq.com/s/yOVbTBVk4CJy6a0lrKjLXA">免费视频教程</a>),这两个都是松哥基于 Spring Boot 来做的,最近松哥打算通过一个系列的文章,来向小伙伴们介绍下如何通过 Spring Boot 来开发公众号后台。</p>
<h2 id="1-缘起">1. 缘起</h2>
<p>今年 5 月份的时候,我想把我自己之前收集到的一些视频教程分享给公众号上的小伙伴,可是这些视频教程大太了,无法一次分享,单次分享分享链接立马就失效了,为了把这些视频分享给大家,我把视频拆分成了很多份,然后设置了不同的口令,小伙伴们在公众号后台通过回复口令就可以获取到这些视频,口令前前后后有 100 多个,我一个一个手动的在微信后台进行配置。这么搞工作量很大,前前后后大概花了三个晚上才把这些东西搞定。</p>
<p>于是我就在想,该写点代码了。</p>
<p>上个月买了服务器,也备案了,该有的都有了,于是就打算把这些资源用代码实现下,因为大学时候搞过公众号开发,倒也没什么难度,于是说干就干。</p>
<h2 id="2-实现思路">2. 实现思路</h2>
<p>其实松哥这个回复口令获取视频链接的实现原理很简单,说白了,就是一个数据查询操作而已,回复的口令是查询关键字,回复的内容则是查询结果。这个原理很简单。</p>
<p>另一方面大家需要明白微信公众号后台开发消息发送的一个流程,大家看下面这张图:</p>
<p><img src="http://www.javaboy.org/images/boot/44-2.jpeg" alt="" /></p>
<p>这是大家在公众号后台回复关键字的情况。那么这个消息是怎么样一个传递流程呢?我们来看看下面这张图:</p>
<p><img src="http://www.javaboy.org/images/boot/44-1.png" alt="" /></p>
<p>这张图,我给大家稍微解释下:</p>
<ol>
<li>首先 <code class="language-plaintext highlighter-rouge">javaboy4096</code> 这个字符从公众号上发送到了微信服务器</li>
<li>接下来微信服务器会把 <code class="language-plaintext highlighter-rouge">javaboy4096</code> 转发到我自己的服务器上</li>
<li>我收到 <code class="language-plaintext highlighter-rouge">javaboy4096</code> 这个字符之后,就去数据库中查询,将查询的结果,按照腾讯要求的 XML 格式进行返回</li>
<li>微信服务器把从我的服务器收到的信息,再发回到微信上,于是小伙伴们就看到了返回结果了</li>
</ol>
<p>大致的流程就是这个样子。</p>
<p>接下来我们就来看一下实现细节。</p>
<h2 id="3-公众号后台配置">3. 公众号后台配置</h2>
<p>开发的第一步,是微信服务器要验证我们自己的服务器是否有效。</p>
<p>首先我们登录微信公众平台官网后,在公众平台官网的 <strong>开发-基本设置</strong> 页面,勾选协议成为开发者,然后点击“修改配置”按钮,填写:</p>
<ul>
<li>服务器地址(URL)</li>
<li>Token</li>
<li>EncodingAESKey</li>
</ul>
<p><img src="http://www.javaboy.org/images/boot/44-3.jpeg" alt="" /></p>
<p>这里的 URL 配置好之后,我们需要针对这个 URL 开发两个接口,一个是 GET 请求的接口,这个接口用来做服务器有效性验证,另一个则是 POST 请求的接口,这个用来接收微信服务器发送来的消息。也就是说,微信服务器的消息都是通过 POST 请求发给我的。</p>
<p>Token 可由开发者可以任意填写,用作生成签名(该 Token 会和接口 URL 中包含的 Token 进行比对,从而验证安全性)。</p>
<p>EncodingAESKey 由开发者手动填写或随机生成,将用作消息体加解密密钥。</p>
<p>同时,开发者可选择消息加解密方式:明文模式、兼容模式和安全模式。明文模式就是我们自己的服务器收到微信服务器发来的消息是明文字符串,直接就可以读取并且解析,安全模式则是我们收到微信服务器发来的消息是加密的消息,需要我们手动解析后才能使用。</p>
<h2 id="4-开发">4. 开发</h2>
<p>公众号后台配置完成后,接下来我们就可以写代码了。</p>
<h3 id="41-服务器有效性校验">4.1 服务器有效性校验</h3>
<p>我们首先来创建一个普通的 Spring Boot 项目,创建时引入 <code class="language-plaintext highlighter-rouge">spring-boot-starter-web</code> 依赖,项目创建成功后,我们创建一个 Controller ,添加如下接口:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/verify_wx_token"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">login</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">response</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">UnsupportedEncodingException</span> <span class="o">{</span>
<span class="n">request</span><span class="o">.</span><span class="na">setCharacterEncoding</span><span class="o">(</span><span class="s">"UTF-8"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">signature</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"signature"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">timestamp</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"timestamp"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">nonce</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"nonce"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">echostr</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"echostr"</span><span class="o">);</span>
<span class="nc">PrintWriter</span> <span class="n">out</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">out</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="na">getWriter</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="nc">CheckUtil</span><span class="o">.</span><span class="na">checkSignature</span><span class="o">(</span><span class="n">signature</span><span class="o">,</span> <span class="n">timestamp</span><span class="o">,</span> <span class="n">nonce</span><span class="o">))</span> <span class="o">{</span>
<span class="n">out</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">echostr</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
<span class="n">out</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>关于这段代码,我做如下解释:</p>
<ol>
<li>首先通过 request.getParameter 方法获取到微信服务器发来的 signature、timestamp、nonce 以及 echostr 四个参数,这四个参数中:signature 表示微信加密签名,signature 结合了开发者填写的 token 参数和请求中的timestamp参数、nonce参数;timestamp 表示时间戳;nonce 表示随机数;echostr 则表示一个随机字符串。</li>
<li>开发者通过检验 signature 对请求进行校验,如果确认此次 GET 请求来自微信服务器,则原样返回 echostr 参数内容,则接入生效,成为开发者成功,否则接入失败。</li>
<li>具体的校验就是松哥这里的 CheckUtil.checkSignature 方法,在这个方法中,首先将token、timestamp、nonce 三个参数进行字典序排序,然后将三个参数字符串拼接成一个字符串进行 sha1 加密,最后开发者获得加密后的字符串可与 signature 对比,标识该请求来源于微信。</li>
</ol>
<p>校验代码如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CheckUtil</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">token</span> <span class="o">=</span> <span class="s">"123456"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">checkSignature</span><span class="o">(</span><span class="nc">String</span> <span class="n">signature</span><span class="o">,</span> <span class="nc">String</span> <span class="n">timestamp</span><span class="o">,</span> <span class="nc">String</span> <span class="n">nonce</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">String</span><span class="o">[]</span> <span class="n">str</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">[]{</span><span class="n">token</span><span class="o">,</span> <span class="n">timestamp</span><span class="o">,</span> <span class="n">nonce</span><span class="o">};</span>
<span class="c1">//排序</span>
<span class="nc">Arrays</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="n">str</span><span class="o">);</span>
<span class="c1">//拼接字符串</span>
<span class="nc">StringBuffer</span> <span class="n">buffer</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringBuffer</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">str</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">buffer</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="n">str</span><span class="o">[</span><span class="n">i</span><span class="o">]);</span>
<span class="o">}</span>
<span class="c1">//进行sha1加密</span>
<span class="nc">String</span> <span class="n">temp</span> <span class="o">=</span> <span class="no">SHA1</span><span class="o">.</span><span class="na">encode</span><span class="o">(</span><span class="n">buffer</span><span class="o">.</span><span class="na">toString</span><span class="o">());</span>
<span class="c1">//与微信提供的signature进行匹对</span>
<span class="k">return</span> <span class="n">signature</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">temp</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SHA1</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">char</span><span class="o">[]</span> <span class="no">HEX_DIGITS</span> <span class="o">=</span> <span class="o">{</span><span class="sc">'0'</span><span class="o">,</span> <span class="sc">'1'</span><span class="o">,</span> <span class="sc">'2'</span><span class="o">,</span> <span class="sc">'3'</span><span class="o">,</span> <span class="sc">'4'</span><span class="o">,</span> <span class="sc">'5'</span><span class="o">,</span>
<span class="sc">'6'</span><span class="o">,</span> <span class="sc">'7'</span><span class="o">,</span> <span class="sc">'8'</span><span class="o">,</span> <span class="sc">'9'</span><span class="o">,</span> <span class="sc">'a'</span><span class="o">,</span> <span class="sc">'b'</span><span class="o">,</span> <span class="sc">'c'</span><span class="o">,</span> <span class="sc">'d'</span><span class="o">,</span> <span class="sc">'e'</span><span class="o">,</span> <span class="sc">'f'</span><span class="o">};</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">getFormattedText</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">bytes</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">bytes</span><span class="o">.</span><span class="na">length</span><span class="o">;</span>
<span class="nc">StringBuilder</span> <span class="n">buf</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringBuilder</span><span class="o">(</span><span class="n">len</span> <span class="o">*</span> <span class="mi">2</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">j</span> <span class="o"><</span> <span class="n">len</span><span class="o">;</span> <span class="n">j</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">buf</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="no">HEX_DIGITS</span><span class="o">[(</span><span class="n">bytes</span><span class="o">[</span><span class="n">j</span><span class="o">]</span> <span class="o">>></span> <span class="mi">4</span><span class="o">)</span> <span class="o">&</span> <span class="mh">0x0f</span><span class="o">]);</span>
<span class="n">buf</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="no">HEX_DIGITS</span><span class="o">[</span><span class="n">bytes</span><span class="o">[</span><span class="n">j</span><span class="o">]</span> <span class="o">&</span> <span class="mh">0x0f</span><span class="o">]);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">buf</span><span class="o">.</span><span class="na">toString</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">encode</span><span class="o">(</span><span class="nc">String</span> <span class="n">str</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">str</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">MessageDigest</span> <span class="n">messageDigest</span> <span class="o">=</span> <span class="nc">MessageDigest</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="s">"SHA1"</span><span class="o">);</span>
<span class="n">messageDigest</span><span class="o">.</span><span class="na">update</span><span class="o">(</span><span class="n">str</span><span class="o">.</span><span class="na">getBytes</span><span class="o">());</span>
<span class="k">return</span> <span class="nf">getFormattedText</span><span class="o">(</span><span class="n">messageDigest</span><span class="o">.</span><span class="na">digest</span><span class="o">());</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>OK,完成之后,我们的校验接口就算是开发完成了。接下来就可以开发消息接收接口了。</p>
<h3 id="42-消息接收接口">4.2 消息接收接口</h3>
<p>接下来我们来开发消息接收接口,消息接收接口和上面的服务器校验接口地址是一样的,都是我们一开始在公众号后台配置的地址。只不过消息接收接口是一个 POST 请求。</p>
<p>我在公众号后台配置的时候,消息加解密方式选择了明文模式,这样我在后台收到的消息直接就可以处理了。微信服务器给我发来的普通文本消息格式如下:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><xml></span>
<span class="nt"><ToUserName></span><span class="cp"><![CDATA[toUser]]></span><span class="nt"></ToUserName></span>
<span class="nt"><FromUserName></span><span class="cp"><![CDATA[fromUser]]></span><span class="nt"></FromUserName></span>
<span class="nt"><CreateTime></span>1348831860<span class="nt"></CreateTime></span>
<span class="nt"><MsgType></span><span class="cp"><![CDATA[text]]></span><span class="nt"></MsgType></span>
<span class="nt"><Content></span><span class="cp"><![CDATA[this is a test]]></span><span class="nt"></Content></span>
<span class="nt"><MsgId></span>1234567890123456<span class="nt"></MsgId></span>
<span class="nt"></xml></span>
</code></pre></div></div>
<p>这些参数含义如下:</p>
<table>
<thead>
<tr>
<th style="text-align: left">参数</th>
<th style="text-align: left">描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">ToUserName</td>
<td style="text-align: left">开发者微信号</td>
</tr>
<tr>
<td style="text-align: left">FromUserName</td>
<td style="text-align: left">发送方帐号(一个OpenID)</td>
</tr>
<tr>
<td style="text-align: left">CreateTime</td>
<td style="text-align: left">消息创建时间 (整型)</td>
</tr>
<tr>
<td style="text-align: left">MsgType</td>
<td style="text-align: left">消息类型,文本为text</td>
</tr>
<tr>
<td style="text-align: left">Content</td>
<td style="text-align: left">文本消息内容</td>
</tr>
<tr>
<td style="text-align: left">MsgId</td>
<td style="text-align: left">消息id,64位整型</td>
</tr>
</tbody>
</table>
<p>看到这里,大家心里大概就有数了,当我们收到微信服务器发来的消息之后,我们就进行 XML 解析,提取出来我们需要的信息,去做相关的查询操作,再将查到的结果返回给微信服务器。</p>
<p>这里我们先来个简单的,我们将收到的消息解析并打印出来:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@PostMapping</span><span class="o">(</span><span class="s">"/verify_wx_token"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">handler</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">response</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">request</span><span class="o">.</span><span class="na">setCharacterEncoding</span><span class="o">(</span><span class="s">"UTF-8"</span><span class="o">);</span>
<span class="n">response</span><span class="o">.</span><span class="na">setCharacterEncoding</span><span class="o">(</span><span class="s">"UTF-8"</span><span class="o">);</span>
<span class="nc">PrintWriter</span> <span class="n">out</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="na">getWriter</span><span class="o">();</span>
<span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">></span> <span class="n">parseXml</span> <span class="o">=</span> <span class="nc">MessageUtil</span><span class="o">.</span><span class="na">parseXml</span><span class="o">(</span><span class="n">request</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">msgType</span> <span class="o">=</span> <span class="n">parseXml</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"MsgType"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">content</span> <span class="o">=</span> <span class="n">parseXml</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"Content"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">fromusername</span> <span class="o">=</span> <span class="n">parseXml</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"FromUserName"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">tousername</span> <span class="o">=</span> <span class="n">parseXml</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"ToUserName"</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">msgType</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">content</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">fromusername</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">tousername</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">></span> <span class="nf">parseXml</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">></span> <span class="n">map</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">>();</span>
<span class="nc">InputStream</span> <span class="n">inputStream</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="na">getInputStream</span><span class="o">();</span>
<span class="nc">SAXReader</span> <span class="n">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SAXReader</span><span class="o">();</span>
<span class="nc">Document</span> <span class="n">document</span> <span class="o">=</span> <span class="n">reader</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="n">inputStream</span><span class="o">);</span>
<span class="nc">Element</span> <span class="n">root</span> <span class="o">=</span> <span class="n">document</span><span class="o">.</span><span class="na">getRootElement</span><span class="o">();</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Element</span><span class="o">></span> <span class="n">elementList</span> <span class="o">=</span> <span class="n">root</span><span class="o">.</span><span class="na">elements</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">Element</span> <span class="n">e</span> <span class="o">:</span> <span class="n">elementList</span><span class="o">)</span>
<span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getName</span><span class="o">(),</span> <span class="n">e</span><span class="o">.</span><span class="na">getText</span><span class="o">());</span>
<span class="n">inputStream</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="n">inputStream</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="k">return</span> <span class="n">map</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>大家看到其实都是一些常规代码,没有什么难度。</p>
<p>做完这些之后,我们将项目打成 jar 包在服务器上部署启动。启动成功之后,确认微信的后台配置也没问题,我们就可以在公众号上发一条消息了,这样我们自己的服务端就会打印出来刚刚消息的信息。</p>
<p>好了,篇幅限制,今天就和大家先聊这么多,后面再聊不同消息类型的解析和消息的返回问题。</p>
<p>不知道小伙伴们看懂没?有问题欢迎留言讨论。</p>
<p>参考资料:微信开放文档</p>江南一点雨wangsong0210@gmail.comHello 各位小伙伴,松哥今天要和大家聊一个有意思的话题,就是使用 Spring Boot 开发微信公众号后台。Spring Security 前后端分离登录,非法请求直接返回 JSON2019-10-16T21:40:59+00:002019-10-16T21:40:59+00:00http://springboot.javaboy.org/2019/1016/springsecurity-login-json<p>hello 各位小伙伴,国庆节终于过完啦,松哥也回来啦,今天开始咱们继续发干货!</p>
<!--more-->
<p>关于 Spring Security,松哥之前发过多篇文章和大家聊聊这个安全框架的使用:</p>
<ol>
<li><a href="https://mp.weixin.qq.com/s/HKJOlatXDS8awBNyCe9JMg">手把手带你入门 Spring Security!</a></li>
<li><a href="https://mp.weixin.qq.com/s/oDow2miLIst-R4NNzc_i4g">Spring Security 登录添加验证码</a></li>
<li><a href="https://mp.weixin.qq.com/s/X1t-VCxzxIcQKOAu-pJrdw">SpringSecurity 登录使用 JSON 格式数据</a></li>
<li><a href="https://mp.weixin.qq.com/s/7D0qJiEIzNuz8VAVvZsXCA">Spring Security 中的角色继承问题</a></li>
<li><a href="https://mp.weixin.qq.com/s/riyFQSrkQBQBCyomE__fLA">Spring Security 中使用 JWT!</a></li>
<li><a href="https://mp.weixin.qq.com/s/1rVPzJGCtDZKvMoA4BYzIA">Spring Security 结合 OAuth2</a></li>
</ol>
<p>不过,今天要和小伙伴们聊一聊 Spring Security 中的另外一个问题,那就是在 Spring Security 中未获认证的请求默认会重定向到登录页,但是在前后端分离的登录中,这个默认行为则显得非常不合适,今天我们主要来看看如何实现未获认证的请求直接返回 JSON ,而不是重定向到登录页面。</p>
<h2 id="前置知识">前置知识</h2>
<p>这里关于 Spring Security 的基本用法我就不再赘述了,如果小伙伴们不了解,可以参考上面的 6 篇文章。</p>
<p>大家知道,在自定义 Spring Security 配置的时候,有这样几个属性:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">configure</span><span class="o">(</span><span class="nc">HttpSecurity</span> <span class="n">http</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">http</span><span class="o">.</span><span class="na">authorizeRequests</span><span class="o">()</span>
<span class="o">.</span><span class="na">anyRequest</span><span class="o">().</span><span class="na">authenticated</span><span class="o">()</span>
<span class="o">.</span><span class="na">formLogin</span><span class="o">()</span>
<span class="o">.</span><span class="na">loginProcessingUrl</span><span class="o">(</span><span class="s">"/doLogin"</span><span class="o">)</span>
<span class="o">.</span><span class="na">loginPage</span><span class="o">(</span><span class="s">"/login"</span><span class="o">)</span>
<span class="c1">//其他配置</span>
<span class="o">.</span><span class="na">permitAll</span><span class="o">()</span>
<span class="o">.</span><span class="na">and</span><span class="o">()</span>
<span class="o">.</span><span class="na">csrf</span><span class="o">().</span><span class="na">disable</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>这里有两个比较重要的属性:</p>
<ul>
<li>loginProcessingUrl:这个表示配置处理登录请求的接口地址,例如你是表单登录,那么 form 表单中 action 的值就是这里填的值。</li>
<li>loginPage:这个表示登录页的地址,例如当你访问一个需要登录后才能访问的资源时,系统就会自动给你通过重定向跳转到这个页面上来。</li>
</ul>
<p>这种配置在前后端不分的登录中是没有问题的,在前后端分离的登录中,这种配置就有问题了。我举个简单的例子,例如我想访问 <code class="language-plaintext highlighter-rouge">/hello</code> 接口,但是这个接口需要登录之后才能访问,我现在没有登录就直接去访问这个接口了,那么系统会给我返回 302,让我去登录页面,在前后端分离中,我的后端一般是没有登录页面的,就是一个提示 JSON,例如下面这样:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/login"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">RespBean</span> <span class="nf">login</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">RespBean</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"尚未登录,请登录!"</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<blockquote>
<p>完整代码大家可以参考我的微人事项目。</p>
</blockquote>
<p>也就是说,当我没有登录直接去访问 <code class="language-plaintext highlighter-rouge">/hello</code> 这个接口的时候,我会看到上面这段 JSON 字符串。在前后端分离开发中,这个看起来没问题(后端不再做页面跳转,无论发生什么都是返回 JSON)。但是问题就出在这里,系统默认的跳转是一个重定向,就是说当你访问 <code class="language-plaintext highlighter-rouge">/hello</code> 的时候,服务端会给浏览器返回 302,同时响应头中有一个 Location 字段,它的值为 <code class="language-plaintext highlighter-rouge">http://localhost:8081/login</code> ,也就是告诉浏览器你去访问 <code class="language-plaintext highlighter-rouge">http://localhost:8081/login</code> 地址吧。浏览器收到指令之后,就会直接去访问 <code class="language-plaintext highlighter-rouge">http://localhost:8081/login</code> 地址,如果此时是开发环境并且请求还是 Ajax 请求,就会发生跨域。因为前后端分离开发中,前端我们一般在 NodeJS 上启动,然后前端的所有请求通过 NodeJS 做请求转发,现在服务端直接把请求地址告诉浏览器了,浏览器就会直接去访问 <code class="language-plaintext highlighter-rouge">http://localhost:8081/login</code> 了,而不会做请求转发了,因此就发生了跨域问题。</p>
<h2 id="解决方案">解决方案</h2>
<p>很明显,上面的问题我们不能用跨域的思路来解决,虽然这种方式看起来也能解决问题,但不是最佳方案。</p>
<p>如果我们的 Spring Security 在用户未获认证的时候去请求一个需要认证后才能请求的数据,此时不给用户重定向,而是直接就返回一个 JSON,告诉用户这个请求需要认证之后才能发起,就不会有上面的事情了。</p>
<p>这里就涉及到 Spring Security 中的一个接口 <code class="language-plaintext highlighter-rouge">AuthenticationEntryPoint</code> ,该接口有一个实现类:<code class="language-plaintext highlighter-rouge">LoginUrlAuthenticationEntryPoint</code> ,该类中有一个方法 <code class="language-plaintext highlighter-rouge">commence</code>,如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* Performs the redirect (or forward) to the login form URL.
*/</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">commence</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">response</span><span class="o">,</span>
<span class="nc">AuthenticationException</span> <span class="n">authException</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">redirectUrl</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">useForward</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">forceHttps</span> <span class="o">&&</span> <span class="s">"http"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">request</span><span class="o">.</span><span class="na">getScheme</span><span class="o">()))</span> <span class="o">{</span>
<span class="n">redirectUrl</span> <span class="o">=</span> <span class="n">buildHttpsRedirectUrlForRequest</span><span class="o">(</span><span class="n">request</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">redirectUrl</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">loginForm</span> <span class="o">=</span> <span class="n">determineUrlToUseForThisRequest</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">,</span>
<span class="n">authException</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">logger</span><span class="o">.</span><span class="na">isDebugEnabled</span><span class="o">())</span> <span class="o">{</span>
<span class="n">logger</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"Server side forward to: "</span> <span class="o">+</span> <span class="n">loginForm</span><span class="o">);</span>
<span class="o">}</span>
<span class="nc">RequestDispatcher</span> <span class="n">dispatcher</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="na">getRequestDispatcher</span><span class="o">(</span><span class="n">loginForm</span><span class="o">);</span>
<span class="n">dispatcher</span><span class="o">.</span><span class="na">forward</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">);</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">else</span> <span class="o">{</span>
<span class="n">redirectUrl</span> <span class="o">=</span> <span class="n">buildRedirectUrlToLoginPage</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">,</span> <span class="n">authException</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">redirectStrategy</span><span class="o">.</span><span class="na">sendRedirect</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">,</span> <span class="n">redirectUrl</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>首先我们从这个方法的注释中就可以看出,这个方法是用来决定到底是要重定向还是要 forward,通过 Debug 追踪,我们发现默认情况下 useForward 的值为 false,所以请求走进了重定向。</p>
<p>那么我们解决问题的思路很简单,直接重写这个方法,在方法中返回 JSON 即可,不再做重定向操作,具体配置如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">configure</span><span class="o">(</span><span class="nc">HttpSecurity</span> <span class="n">http</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">http</span><span class="o">.</span><span class="na">authorizeRequests</span><span class="o">()</span>
<span class="o">.</span><span class="na">anyRequest</span><span class="o">().</span><span class="na">authenticated</span><span class="o">()</span>
<span class="o">.</span><span class="na">formLogin</span><span class="o">()</span>
<span class="o">.</span><span class="na">loginProcessingUrl</span><span class="o">(</span><span class="s">"/doLogin"</span><span class="o">)</span>
<span class="o">.</span><span class="na">loginPage</span><span class="o">(</span><span class="s">"/login"</span><span class="o">)</span>
<span class="c1">//其他配置</span>
<span class="o">.</span><span class="na">permitAll</span><span class="o">()</span>
<span class="o">.</span><span class="na">and</span><span class="o">()</span>
<span class="o">.</span><span class="na">csrf</span><span class="o">().</span><span class="na">disable</span><span class="o">().</span><span class="na">exceptionHandling</span><span class="o">()</span>
<span class="o">.</span><span class="na">authenticationEntryPoint</span><span class="o">(</span><span class="k">new</span> <span class="nc">AuthenticationEntryPoint</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">commence</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">req</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">resp</span><span class="o">,</span> <span class="nc">AuthenticationException</span> <span class="n">authException</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span><span class="o">,</span> <span class="nc">ServletException</span> <span class="o">{</span>
<span class="n">resp</span><span class="o">.</span><span class="na">setContentType</span><span class="o">(</span><span class="s">"application/json;charset=utf-8"</span><span class="o">);</span>
<span class="nc">PrintWriter</span> <span class="n">out</span> <span class="o">=</span> <span class="n">resp</span><span class="o">.</span><span class="na">getWriter</span><span class="o">();</span>
<span class="nc">RespBean</span> <span class="n">respBean</span> <span class="o">=</span> <span class="nc">RespBean</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"访问失败!"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">authException</span> <span class="k">instanceof</span> <span class="nc">InsufficientAuthenticationException</span><span class="o">)</span> <span class="o">{</span>
<span class="n">respBean</span><span class="o">.</span><span class="na">setMsg</span><span class="o">(</span><span class="s">"请求失败,请联系管理员!"</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">out</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="k">new</span> <span class="nc">ObjectMapper</span><span class="o">().</span><span class="na">writeValueAsString</span><span class="o">(</span><span class="n">respBean</span><span class="o">));</span>
<span class="n">out</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span>
<span class="n">out</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="o">}</span>
</code></pre></div></div>
<p>在 Spring Security 的配置中加上自定义的 <code class="language-plaintext highlighter-rouge">AuthenticationEntryPoint</code> 处理方法,该方法中直接返回相应的 JSON 提示即可。这样,如果用户再去直接访问一个需要认证之后才可以访问的请求,就不会发生重定向操作了,服务端会直接给浏览器一个 JSON 提示,浏览器收到 JSON 之后,该干嘛干嘛。</p>
<h2 id="结语">结语</h2>
<p>好了,一个小小的重定向问题和小伙伴们分享下,不知道大家有没有看懂呢?这也是我最近在重构微人事的时候遇到的问题。预计 11 月份,微人事的 Spring Boot 版本会升级到目前最新版,请小伙伴们留意哦。</p>江南一点雨wangsong0210@gmail.comhello 各位小伙伴,国庆节终于过完啦,松哥也回来啦,今天开始咱们继续发干货!Spring Boot 中自定义 SpringMVC 配置,到底继承谁?2019-09-12T21:40:59+00:002019-09-12T21:40:59+00:00http://springboot.javaboy.org/2019/0912/springmvc-config<p>用过 Spring Boot 的小伙伴都知道,我们只需要在项目中引入 <code class="language-plaintext highlighter-rouge">spring-boot-starter-web</code> 依赖,SpringMVC 的一整套东西就会自动给我们配置好,但是,真实的项目环境比较复杂,系统自带的配置不一定满足我们的需求,往往我们还需要结合实际情况自定义配置。</p>
<!--more-->
<p>自定义配置就有讲究了,由于 Spring Boot 的版本变迁,加上这一块本身就有几个不同写法,很多小伙伴在这里容易搞混,今天松哥就来和大家说一说这个问题。</p>
<h2 id="概览">概览</h2>
<p>首先我们需要明确,跟自定义 SpringMVC 相关的类和注解主要有如下四个:</p>
<ul>
<li>WebMvcConfigurerAdapter</li>
<li>WebMvcConfigurer</li>
<li>WebMvcConfigurationSupport</li>
<li>@EnableWebMvc</li>
</ul>
<p>这四个中,除了第四个是注解,另外三个两个类一个接口,里边的方法看起来好像都类似,但是实际使用效果却大不相同,因此很多小伙伴容易搞混,今天松哥就来和大家聊一聊这个问题。</p>
<h2 id="webmvcconfigureradapter">WebMvcConfigurerAdapter</h2>
<p>我们先来看 WebMvcConfigurerAdapter,这个是在 Spring Boot 1.x 中我们自定义 SpringMVC 时继承的一个抽象类,这个抽象类本身是实现了 WebMvcConfigurer 接口,然后抽象类里边都是空方法,我们来看一下这个类的声明:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">WebMvcConfigurerAdapter</span> <span class="kd">implements</span> <span class="nc">WebMvcConfigurer</span> <span class="o">{</span>
<span class="c1">//各种 SpringMVC 配置的方法</span>
<span class="o">}</span>
</code></pre></div></div>
<p>再来看看这个类的注释:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* An implementation of {@link WebMvcConfigurer} with empty methods allowing
* subclasses to override only the methods they're interested in.
* @deprecated as of 5.0 {@link WebMvcConfigurer} has default methods (made
* possible by a Java 8 baseline) and can be implemented directly without the
* need for this adapter
*/</span>
</code></pre></div></div>
<p>这段注释关于这个类说的很明白了。同时我们也看到,从 Spring5 开始,由于我们要使用 Java8,而 Java8 中的接口允许存在 default 方法,因此官方建议我们直接实现 WebMvcConfigurer 接口,而不是继承 WebMvcConfigurerAdapter 。</p>
<p><strong>也就是说,在 Spring Boot 1.x 的时代,如果我们需要自定义 SpringMVC 配置,直接继承 WebMvcConfigurerAdapter 类即可。</strong></p>
<h2 id="webmvcconfigurer">WebMvcConfigurer</h2>
<p>根据上一小节的解释,小伙伴们已经明白了,WebMvcConfigurer 是我们在 Spring Boot 2.x 中实现自定义配置的方案。</p>
<p>WebMvcConfigurer 是一个接口,接口中的方法和 WebMvcConfigurerAdapter 中定义的空方法其实一样,所以用法上来说,基本上没有差别,从 Spring Boot 1.x 切换到 Spring Boot 2.x ,只需要把继承类改成实现接口即可。</p>
<p>松哥在之前的案例中(<a href="https://mp.weixin.qq.com/s/tm1IqiEvRZwDAb-F5yJ5Aw">40 篇原创干货,带你进入 Spring Boot 殿堂!</a>),凡是涉及到自定义 SpringMVC 配置的地方,也都是通过实现 WebMvcConfigurer 接口来完成的。</p>
<h2 id="webmvcconfigurationsupport">WebMvcConfigurationSupport</h2>
<p>前面两个都好理解,还有一个 WebMvcConfigurationSupport ,这个又是干什么用的呢?</p>
<p>松哥之前有一篇文章中用过这个类,不知道小伙伴们有没有留意,就是下面这篇:</p>
<ul>
<li><a href="https://mp.weixin.qq.com/s/NC_0oaeBzRjCB34U_ZWxIQ">纯 Java 代码搭建 SSM 环境</a></li>
</ul>
<p>这篇文章我放弃了 Spring 和 SpringMVC 的 xml 配置文件,转而用 Java 代替这两个 xml 配置。那么在这里我自定义 SpringMVC 配置的时候,就是通过继承 WebMvcConfigurationSupport 类来实现的。在 WebMvcConfigurationSupport 类中,提供了用 Java 配置 SpringMVC 所需要的所有方法。我们来看一下这个方法的摘要:</p>
<p><img src="http://www.javaboy.org/images/boot/37-1.png" alt="" /></p>
<p>有一点眼熟,可能有小伙伴发现了,这里的方法其实和前面两个类中的方法基本是一样的。</p>
<p>在这里首先大家需要明确的是,WebMvcConfigurationSupport 类本身是没有问题的,我们自定义 SpringMVC 的配置是可以通过继承 WebMvcConfigurationSupport 来实现的。但是继承 WebMvcConfigurationSupport 这种操作我们一般只在 Java 配置的 SSM 项目中使用,Spring Boot 中基本上不会这么写,为什么呢?</p>
<p>小伙伴们知道,Spring Boot 中,SpringMVC 相关的自动化配置是在 WebMvcAutoConfiguration 配置类中实现的,那么我们来看看这个配置类的生效条件:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="nd">@ConditionalOnWebApplication</span><span class="o">(</span><span class="n">type</span> <span class="o">=</span> <span class="nc">Type</span><span class="o">.</span><span class="na">SERVLET</span><span class="o">)</span>
<span class="nd">@ConditionalOnClass</span><span class="o">({</span> <span class="nc">Servlet</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">DispatcherServlet</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">WebMvcConfigurer</span><span class="o">.</span><span class="na">class</span> <span class="o">})</span>
<span class="nd">@ConditionalOnMissingBean</span><span class="o">(</span><span class="nc">WebMvcConfigurationSupport</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@AutoConfigureOrder</span><span class="o">(</span><span class="nc">Ordered</span><span class="o">.</span><span class="na">HIGHEST_PRECEDENCE</span> <span class="o">+</span> <span class="mi">10</span><span class="o">)</span>
<span class="nd">@AutoConfigureAfter</span><span class="o">({</span> <span class="nc">DispatcherServletAutoConfiguration</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">TaskExecutionAutoConfiguration</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
<span class="nc">ValidationAutoConfiguration</span><span class="o">.</span><span class="na">class</span> <span class="o">})</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">WebMvcAutoConfiguration</span> <span class="o">{</span>
<span class="o">}</span>
</code></pre></div></div>
<p>我们从这个类的注解中可以看到,它的生效条件有一条,就是当不存在 WebMvcConfigurationSupport 的实例时,这个自动化配置才会生生效。因此,如果我们在 Spring Boot 中自定义 SpringMVC 配置时选择了继承 WebMvcConfigurationSupport,就会导致 Spring Boot 中 SpringMVC 的自动化配置失效。</p>
<p><strong>Spring Boot 给我们提供了很多自动化配置,很多时候当我们修改这些配置的时候,并不是要全盘否定 Spring Boot 提供的自动化配置,我们可能只是针对某一个配置做出修改,其他的配置还是按照 Spring Boot 默认的自动化配置来,而继承 WebMvcConfigurationSupport 来实现对 SpringMVC 的配置会导致所有的 SpringMVC 自动化配置失效,因此,一般情况下我们不选择这种方案。</strong></p>
<p>在 Java 搭建的 SSM 项目中(<a href="https://mp.weixin.qq.com/s/NC_0oaeBzRjCB34U_ZWxIQ">纯 Java 代码搭建 SSM 环境</a>),因为本身就没什么自动化配置,所以我们使用了继承 WebMvcConfigurationSupport。</p>
<h2 id="enablewebmvc">@EnableWebMvc</h2>
<p>最后还有一个 @EnableWebMvc 注解,这个注解很好理解,它的作用就是启用 WebMvcConfigurationSupport。我们来看看这个注解的定义:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">/**</span>
<span class="o">*</span> <span class="nc">Adding</span> <span class="k">this</span> <span class="n">annotation</span> <span class="n">to</span> <span class="n">an</span> <span class="o">{</span><span class="nd">@code</span> <span class="nd">@Configuration</span><span class="o">}</span> <span class="kd">class</span> <span class="nc">imports</span> <span class="n">the</span> <span class="nc">Spring</span> <span class="no">MVC</span>
<span class="o">*</span> <span class="n">configuration</span> <span class="n">from</span> <span class="o">{</span><span class="nd">@link</span> <span class="nc">WebMvcConfigurationSupport</span><span class="o">},</span> <span class="n">e</span><span class="o">.</span><span class="na">g</span><span class="o">.:</span>
</code></pre></div></div>
<p>可以看到,加了这个注解,就会自动导入 WebMvcConfigurationSupport,所以在 Spring Boot 中,我们也不建议使用 @EnableWebMvc 注解,因为它一样会导致 Spring Boot 中的 SpringMVC 自动化配置失效。</p>
<h2 id="总结">总结</h2>
<p>不知道上面的解释小伙伴有没有看懂?我再简单总结一下:</p>
<ol>
<li>Spring Boot 1.x 中,自定义 SpringMVC 配置可以通过继承 WebMvcConfigurerAdapter 来实现。</li>
<li>Spring Boot 2.x 中,自定义 SpringMVC 配置可以通过实现 WebMvcConfigurer 接口来完成。</li>
<li>如果在 Spring Boot 中使用继承 WebMvcConfigurationSupport 来实现自定义 SpringMVC 配置,或者在 Spring Boot 中使用了 @EnableWebMvc 注解,都会导致 Spring Boot 中默认的 SpringMVC 自动化配置失效。</li>
<li>在纯 Java 配置的 SSM 环境中,如果我们要自定义 SpringMVC 配置,有两种办法,第一种就是直接继承自 WebMvcConfigurationSupport 来完成 SpringMVC 配置,还有一种方案就是实现 WebMvcConfigurer 接口来完成自定义 SpringMVC 配置,如果使用第二种方式,则需要给 SpringMVC 的配置类上额外添加 @EnableWebMvc 注解,表示启用 WebMvcConfigurationSupport,这样配置才会生效。换句话说,在纯 Java 配置的 SSM 中,如果你需要自定义 SpringMVC 配置,你离不开 WebMvcConfigurationSupport ,所以在这种情况下建议通过继承 WebMvcConfigurationSupport 来实现自动化配置。</li>
</ol>
<p>不知道小伙伴们有没有看懂呢?有问题欢迎留言讨论。</p>江南一点雨wangsong0210@gmail.com用过 Spring Boot 的小伙伴都知道,我们只需要在项目中引入 spring-boot-starter-web 依赖,SpringMVC 的一整套东西就会自动给我们配置好,但是,真实的项目环境比较复杂,系统自带的配置不一定满足我们的需求,往往我们还需要结合实际情况自定义配置。一键部署 Spring Boot 到远程 Docker 容器,就是这么秀!2019-08-30T21:40:59+00:002019-08-30T21:40:59+00:00http://springboot.javaboy.org/2019/0830/springboot-docker<p>不知道各位小伙伴在生产环境都是怎么部署 Spring Boot 的,打成 jar 直接一键运行?打成 war 扔到 Tomcat 容器中运行?不过据松哥了解,容器化部署应该是目前的主流方案。</p>
<!--more-->
<p>不同于传统的单体应用,微服务由于服务数量众多,在部署的时候出问题的可能性更大,这个时候,结合 Docker 来部署,就可以很好的解决这个问题,这也是目前使用较多的方案之一。</p>
<p>将 Spring Boot 项目打包到 Docker 容器中部署,有很多不同的方法,今天松哥主要来和大家聊一聊如何将 Spring Boot 项目一键打包到远程 Docker 容器,然后通过运行一个镜像的方式来启动一个 Spring Boot 项目。</p>
<p>至于其他的 Spring Boot 结合 Docker 的用法,大家不要着急,后续的文章,松哥会和大家慢慢的一一道来。</p>
<h2 id="1准备工作">1.准备工作</h2>
<h3 id="11-准备-docker">1.1 准备 Docker</h3>
<p>我这里以 CentOS7 为例来给大家演示。</p>
<p>首先需要在 CentOS7 上安装好 Docker,这个安装方式网上很多,我就不多说了,我自己去年写过一个 Docker 入门教程,大家可以在公众号后台回复 <code class="language-plaintext highlighter-rouge">Docker</code> 获取教程下载地址。</p>
<p>Docker 安装成功之后,我们首先需要修改 Docker 配置,开启允许远程访问 Docker 的功能,开启方式很简单,修改 <code class="language-plaintext highlighter-rouge">/usr/lib/systemd/system/docker.service</code> 文件,加入如下内容:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
</code></pre></div></div>
<p>如下图:</p>
<p><img src="http://www.javaboy.org/images/boot/34-1.png" alt="" /></p>
<p>配置完成后,保存退出,然后重启 Docker:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl daemon-reload
service docker restart
</code></pre></div></div>
<p>Docker 重启成功之后,Docker 的准备工作就算是 OK 了。</p>
<h3 id="12-准备-idea">1.2 准备 IDEA</h3>
<p>IDEA 上的准备工作,主要是安装一个 Docker 插件,点击 <code class="language-plaintext highlighter-rouge">File->Settings->Plugins->Browse Repositories</code> 如下:</p>
<p><img src="http://www.javaboy.org/images/boot/34-2.png" alt="" /></p>
<p>点击右边绿色的 Install 按钮,完成安装,安装完成之后需要重启一下 IDEA。</p>
<p>IDEA 重启成功之后,我们依次打开 <code class="language-plaintext highlighter-rouge">File->Settings->Build,Execution,Deployment->Docker</code> ,然后配置一下 Docker 的远程连接地址:</p>
<p><img src="http://www.javaboy.org/images/boot/34-3.png" alt="" /></p>
<p>配置一下 Docker 的地址,配置完成后,可以看到下面有一个 Connection successful 提示,这个表示 Docker 已经连接上了。</p>
<p>如此之后,我们的准备工作就算是 OK 了。</p>
<h2 id="2准备项目">2.准备项目</h2>
<p>接下来我们来创建一个简单的 Spring Boot 项目(只需要引入 <code class="language-plaintext highlighter-rouge">spring-boot-starter-web</code> 依赖即可),项目创建成功之后,我们再创建一个普通的 <code class="language-plaintext highlighter-rouge">HelloDockerController</code>,用来做测试,如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestController</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">HelloDockerController</span> <span class="o">{</span>
<span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/hello"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">hello</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="s">"hello docker!"</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>这是一个很简单的接口,无需多说。</p>
<h2 id="3配置-dockerfile">3.配置 Dockerfile</h2>
<p>接下来,在项目的根目录下,我创建一个 Dockerfile ,作为我镜像的构建文件,具体位置如下图:</p>
<p><img src="http://www.javaboy.org/images/boot/34-4.png" alt="" /></p>
<p>文件内容如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">FROM</span> <span class="n">hub</span><span class="o">.</span><span class="na">c</span><span class="o">.</span><span class="mi">163</span><span class="o">.</span><span class="na">com</span><span class="o">/</span><span class="n">library</span><span class="o">/</span><span class="nl">java:</span><span class="n">latest</span>
<span class="no">VOLUME</span> <span class="o">/</span><span class="n">tmp</span>
<span class="no">ADD</span> <span class="n">target</span><span class="o">/</span><span class="n">docker</span><span class="o">-</span><span class="mf">0.0</span><span class="o">.</span><span class="mi">1</span><span class="o">-</span><span class="no">SNAPSHOT</span><span class="o">.</span><span class="na">jar</span> <span class="n">app</span><span class="o">.</span><span class="na">jar</span>
<span class="no">ENTRYPOINT</span> <span class="o">[</span><span class="s">"java"</span><span class="o">,</span><span class="s">"-jar"</span><span class="o">,</span><span class="s">"/app.jar"</span><span class="o">]</span>
</code></pre></div></div>
<p>这里只有简单的四行,我说一下:</p>
<ol>
<li>Spring Boot 项目的运行依赖 Java 环境,所以我自己的镜像基于 Java 镜像来构建。</li>
<li>考虑到 Docker 官方镜像下载较慢,我这里使用了网易提供的 Docker 镜像。</li>
<li>由于 Spring Boot 运行时需要 tmp 目录,这里数据卷配置一个 /tmp 目录出来。</li>
<li>将本地 target 目录中打包好的 .jar 文件复制一份新的 到 /app.jar。</li>
<li>最后就是配置一下启动命令,由于我打包的 jar 已经成为 app.jar 了,所以启动命令也是启动 app.jar。</li>
</ol>
<p>这是我们配置的一个简单的 Dockerfile。</p>
<h2 id="4配置-maven-插件">4.配置 Maven 插件</h2>
<p>接下来在 pom.xml 文件中,添加如下插件:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><plugin></span>
<span class="nt"><groupId></span>com.spotify<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>docker-maven-plugin<span class="nt"></artifactId></span>
<span class="nt"><version></span>1.2.0<span class="nt"></version></span>
<span class="nt"><executions></span>
<span class="nt"><execution></span>
<span class="nt"><id></span>build-image<span class="nt"></id></span>
<span class="nt"><phase></span>package<span class="nt"></phase></span>
<span class="nt"><goals></span>
<span class="nt"><goal></span>build<span class="nt"></goal></span>
<span class="nt"></goals></span>
<span class="nt"></execution></span>
<span class="nt"></executions></span>
<span class="nt"><configuration></span>
<span class="nt"><dockerHost></span>http://192.168.66.131:2375<span class="nt"></dockerHost></span>
<span class="nt"><imageName></span>javaboy/${project.artifactId}<span class="nt"></imageName></span>
<span class="nt"><imageTags></span>
<span class="nt"><imageTag></span>${project.version}<span class="nt"></imageTag></span>
<span class="nt"></imageTags></span>
<span class="nt"><forceTags></span>true<span class="nt"></forceTags></span>
<span class="nt"><dockerDirectory></span>${project.basedir}<span class="nt"></dockerDirectory></span>
<span class="nt"><resources></span>
<span class="nt"><resource></span>
<span class="nt"><targetPath></span>/<span class="nt"></targetPath></span>
<span class="nt"><directory></span>${project.build.directory}<span class="nt"></directory></span>
<span class="nt"><include></span>${project.build.finalName}.jar<span class="nt"></include></span>
<span class="nt"></resource></span>
<span class="nt"></resources></span>
<span class="nt"></configuration></span>
<span class="nt"></plugin></span>
</code></pre></div></div>
<p>这个插件的配置不难理解:</p>
<ol>
<li>首先在 execution 节点中配置当执行 mvn package 的时候,顺便也执行一下 docker:build</li>
<li>然后在 configuration 中分别配置 Docker 的主机地址,镜像的名称,镜像的 tags,其中 dockerDirectory 表示指定 Dockerfile 的位置。</li>
<li>最后 resource 节点中再配置一下 jar 的位置和名称即可。</li>
</ol>
<p>OK,做完这些我们就算大功告成了。</p>
<h2 id="5打包运行">5.打包运行</h2>
<p>接下来对项目进行打包,打包完成后,项目会自动构建成一个镜像,并且上传到 Docker 容器中,打包方式如下:</p>
<p><img src="http://www.javaboy.org/images/boot/34-5.png" alt="" /></p>
<p>打包过程会稍微有一点旧,因为还包含了镜像的构建,特别是第一次打包,需要下载基础镜像,会更慢一些。</p>
<p>部分打包日志如下(项目构建过程):</p>
<p><img src="http://www.javaboy.org/images/boot/34-6.png" alt="" /></p>
<p>项目打包成功之后,我们就可以在 Docker 容器中看到我们刚刚打包成的镜像了,如下:</p>
<p><img src="http://www.javaboy.org/images/boot/34-7.png" alt="" /></p>
<h3 id="51-运行方式一">5.1 运行方式一</h3>
<p>此时,我们可以直接在 Linux 上像创建普通容器一样创建这个镜像的容器,然后启动,执行如下命令即可:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -d --name javaboy -p 8080:8080 javaboy/docker:0.0.1
</code></pre></div></div>
<p>启动成功之后,我们就可以访问容器中的接口了。</p>
<p>但是这种操作显然还是有点麻烦,结合我们一开始安装的 Docker 插件,这个运行步骤还可以做进一步的简化。</p>
<h3 id="52-运行方式二">5.2 运行方式二</h3>
<p>大家注意,此时我们的 IDEA 中多了一个选项,就是 docker,如下:</p>
<p><img src="http://www.javaboy.org/images/boot/34-8.png" alt="" /></p>
<p>点击左边的绿色启动按钮,连接上 Docker 容器,连接成功之后,我们就可以看到目前 Docker 中的所有容器和镜像了,当然也包括我们刚刚创建的 Docker 镜像,如下:</p>
<p><img src="http://www.javaboy.org/images/boot/34-9.png" alt="" /></p>
<p>此时,我们选中这个镜像,右键单击,即可基于此镜像创建出一个容器,如下图:</p>
<p><img src="http://www.javaboy.org/images/boot/34-10.png" alt="" /></p>
<p>我们选择 Create container,然后填入容器的一些必要信息,配置一下容器名称,镜像 ID 会自动填上,暴露的端口使用 Specify 即可,然后写上端口的映射关系:</p>
<p><img src="http://www.javaboy.org/images/boot/34-11.png" alt="" /></p>
<p>配置完成后,点击下方的 <code class="language-plaintext highlighter-rouge">run</code> 按钮,就可以开始运行了。运行日志如下:</p>
<p><img src="http://www.javaboy.org/images/boot/34-12.png" alt="" /></p>
<p>注意,这个日志是在 Docker 的那个窗口里打印出来的。</p>
<p>项目运行成功之后,在浏览器输入远程服务器的地址,就可以访问了:</p>
<p><img src="http://www.javaboy.org/images/boot/34-13.png" alt="" /></p>
<p>如此之后,我们的 Spring Boot 项目就算顺利发布到远程 Docker 容器中了。</p>
<p>好玩吗?试试!</p>
<p>本文案例我已经上传到 GitHub,小伙伴们可以参考:https://github.com/lenve/javaboy-code-samples</p>江南一点雨wangsong0210@gmail.com不知道各位小伙伴在生产环境都是怎么部署 Spring Boot 的,打成 jar 直接一键运行?打成 war 扔到 Tomcat 容器中运行?不过据松哥了解,容器化部署应该是目前的主流方案。完结撒花!129 集 21 个小时,松哥自制的 Spring Boot2 系列视频教程杀青啦!2019-08-26T21:40:59+00:002019-08-26T21:40:59+00:00http://springboot.javaboy.org/2019/0826/springboot-video<p>松哥的 Spring Boot 教程分为几个阶段。</p>
<!--more-->
<h2 id="2016">2016</h2>
<p>松哥最早在 2016 年底的时候开始写 Spring Boot 系列的教程,记得当时在广州上班,年底那段时间在深圳出差,在深圳人生地不熟,下班回到酒店,就开始写博客,写 Spring Boot 教程。</p>
<p>我写的 Spring Boot 教程,不敢说是顶呱呱,但是我相信对大家来说绝对是有用的。我在 CSDN 上写了 400 多篇原创干货,其中访问量最高的几篇竟然都是 Spring Boot 相关的:</p>
<p><img src="http://www.javaboy.org/images/boot/35-1.png" alt="" /></p>
<p>Spring Boot 火爆程度可见一斑。不过这些都是基于这是基于早期的 Spring Boot 版本写的(1.4.x)。</p>
<p>Spring Boot 也算是业界有名的版本帝,版本更新非常快,这也从侧面说明了 Spring Boot 发展速度之快。于是松哥的教程一直没有停。</p>
<h2 id="2017">2017</h2>
<p>我在 2017 年推出了两个 Spring Boot + Vue 前后端分离项目。目前在 GitHub 上 star 数分别超过 8.8k 和 2.8k(<a href="https://mp.weixin.qq.com/s/qGFo2MKkD0AObBJDPR8veQ">公司倒闭 1 年了,而我当年的项目上了 GitHub 热榜</a>)。</p>
<ul>
<li>https://github.com/lenve/vhr</li>
<li>https://github.com/lenve/VBlog</li>
</ul>
<p>为什么这两个项目这么火呢?我也分析过原因,单纯的 Spring Boot 并不难,单纯的 Vue 也不难,相对于 React 和 Angular ,Vue 算是最容易上手的前端框架了。但是要把前后端结合起来,这就有难度了,对前端工程师而言,数据库、Java、Redis 等等,都要花时间去学习,对后端工程师而言,前端的 ES6、webpack、前端工程化、Vue 等等也都要花时间去研究。</p>
<p>而我这两个开源项目,则打通了前后端,从一个 Java 工程师的角度,带领小伙伴既写后端接口,又写前端页面,快速实现一个常规的企业后台管理系统。</p>
<p>时代变了,单兵作战、快速迭代才有未来。从这个角度来讲,每个人都不应只专注于后端的 CRUD,我觉得这是这两个开源项目受欢迎的原因。</p>
<h2 id="2018">2018</h2>
<p>2018 年,应清华社夏老师的邀请,出版了 《Spring Boot + Vue 全栈开发实战》 一书。把 Spring Boot 开发中的知识点做了一番仔细的整理,同时也在自己脑海中将 Spring Boot 教程体系化。</p>
<p>新书出版至今,已经加印多次,还被国内某一本大学选作教材(<a href="https://mp.weixin.qq.com/s/IJZukUhu-Rec8lF9KQseoQ">我的第一本书,被选作大学教材了!</a>)。</p>
<p>加了很多读者的微信,也收到读者不少反馈。我发现一些很简单的知识点,大家照着书写还是有问题,虽然我也提供了很多配套案例,可是还是会收到不少小伙伴的求助,很多东西搞不定。</p>
<p>于是,继续出教程….</p>
<h2 id="2019">2019</h2>
<p>时间到了 2019 年,Spring Boot 又经过了好几次版本变更,我自己也写了不少新版教程:</p>
<ul>
<li><a href="https://mp.weixin.qq.com/s/tm1IqiEvRZwDAb-F5yJ5Aw">40 篇原创干货,带你进入 Spring Boot 殿堂!</a></li>
</ul>
<p>还利用业余时间整理了一个电子书出来:</p>
<p><img src="http://www.javaboy.org/images/boot/35-2.png" alt="" /></p>
<p>可以说,在 Spring Boot 布道的路上从未停止。</p>
<p>除了这些图文教程之外,松哥最近也抽时间录制了一套 Spring Boot 视频教程,这套教程分为两个阶段:</p>
<ol>
<li>Spring Boot 精讲系列</li>
<li>Spring Boot + Vue 项目实战系列</li>
</ol>
<p>目前第一阶段的视频已经录制完毕,共 129 集 21 个小时,全程高能无废话,可以说是满满的干货,大家可以看一下目录:</p>
<p><img src="http://www.javaboy.org/images/boot/35-3.png" alt="" /></p>
<p>从 9 月份开始,我将开始录制第二阶段的内容,第二阶段我会手把手带领大家做一个 Spring Boot + Vue 的实战项目,具体的项目就是我在 GitHub 上的开源项目 vhr(https://github.com/lenve/vhr),该项目目前已经超过 8.8k star。这个项目我会带领大家从头开始搭建 Spring Boot + Vue 前后端分离环境,权限设计,RESTful 接口设计等,预计两个月之内更新完毕。</p>
<p>看过我博客的小伙伴应该知道,我的博客的思路清晰,小伙伴按照我博客的思路都能够做出来效果,我的视频教程和博客的风格一致,一样也是思路清晰条理清楚,这不是我的自夸,有小伙伴的评价为证:</p>
<p><img src="http://www.javaboy.org/images/boot/35-4.jpg" alt="" />
<img src="http://www.javaboy.org/images/boot/35-5.jpg" alt="" />
<img src="http://www.javaboy.org/images/boot/35-6.jpg" alt="" /></p>
<p>除了这两个视频之外,后期也会录制其他视频教程,目前确定的有 Cloud 和 Redis,其他的还在规划中,不过可以确定的是,每个月都会发布我自己录制的视频教程。</p>
<p>这些视频的录制,我花费了巨大的时间成本,很多时候我都是晚上十二点才到家,然后早上六点起来录视频,录到八点半,然后去上班:</p>
<p><img src="http://www.javaboy.org/images/boot/35-7.jpeg" alt="" /></p>
<p>晚上回到家,除了写博客,还要对录好的视频剪辑,去噪,这是一个细活:</p>
<p><img src="http://www.javaboy.org/images/boot/35-8.jpeg" alt="" />
<img src="http://www.javaboy.org/images/boot/35-9.jpeg" alt="" /></p>
<p>巨大的时间付出,保证了视频的质量,当然也决定了这是一套付费视频。</p>
<p>我自己还在网上搜集了很多别人录制的视频,这些视频对我来说没有多大成本,都是网络上找的,因此我都免费送给大家了,在我公众号底部菜单里有免费视频,这些免费视频大家都可以领取,我不会拿这些随处可见的视频来卖钱。</p>
<p>如果大家想要试看视频,可以参考如下两篇文章:</p>
<ul>
<li><a href="https://mp.weixin.qq.com/s/sgjm09_e8ue5blXqPgeXZA">Spring Boot 整合 Spring Session</a></li>
<li><a href="https://mp.weixin.qq.com/s/GcMuJ23WVvo2s_6LSgR4fA">Spring Boot 中 CORS 解决跨域</a></li>
</ul>
<p>欢迎大家加入星球,一起学习进步!现在直接扫码加入星球需要 199,这里我提供另外一个优惠的方式,大家可以加我微信,发红包只要 119,然后我手动拉你进星球。星球上每有一个课程完结的时候,就会提升一次价格,早点加入就是优势。<strong>同时为了保证加入星球的小伙伴的权益,我可以向大家保证,你在其他地方不会看到一模一样的免费的整套视频教程,因为这些视频都是我自己录制的,全部都是加密之后发布的,所以请大家放心。</strong></p>
<p><img src="http://www.javaboy.org/images/boot/35-10.jpeg" alt="" />
<img src="http://www.javaboy.org/images/boot/35-11.png" alt="" /></p>
<p>感谢大家信任。</p>江南一点雨wangsong0210@gmail.com松哥的 Spring Boot 教程分为几个阶段。40 篇原创干货,带你进入 Spring Boot 殿堂!2019-08-20T21:40:59+00:002019-08-20T21:40:59+00:00http://springboot.javaboy.org/2019/0820/springboot-guide<p>两个月前,松哥总结过一次已经完成的 Spring Boot 教程,当时感受到了小伙伴们巨大的热情。</p>
<!--more-->
<p>两个月过去了,松哥的 Spring Boot 教程又更新了不少,为了方便小伙伴们查找,这里再给大家做一个索引参考。</p>
<p>需要再次说明的是,这一系列教程不是终点,而是一个起点,松哥后期还会不断完善这个教程,也会持续更新 Spring Boot 最新版本的教程,希望能帮到大家。教程索引如下:</p>
<h2 id="spring-boot2-教程合集">Spring Boot2 教程合集</h2>
<h3 id="入门">入门</h3>
<ol>
<li><a href="https://mp.weixin.qq.com/s/NC_0oaeBzRjCB34U_ZWxIQ">纯 Java 代码搭建 SSM 环境</a></li>
<li><a href="https://mp.weixin.qq.com/s/FMVut8slVZJdxxLf3Y6jLw">创建一个 Spring Boot 项目的三种方法</a></li>
<li><a href="https://mp.weixin.qq.com/s/2w6B4fMdbTK_mGjnaMG4BQ">理解 Spring Boot 项目中的 parent</a></li>
</ol>
<h3 id="基础配置">基础配置</h3>
<ol>
<li><a href="https://mp.weixin.qq.com/s/cUhzpo8zkQq09d8S4WkAsw">配置文件 application.properties</a></li>
<li><a href="https://mp.weixin.qq.com/s/dbSBzFICIDPLkj5Tuv2-yA">yaml配置简介</a></li>
<li><a href="https://mp.weixin.qq.com/s/WOmOXN_IK0IMjL0_hlAOFA">Spring Boot 支持 Https</a></li>
<li><a href="https://mp.weixin.qq.com/s/tKr_shLQnvcQADr4mvcU3A">徒手撸一个 Spring Boot 中的 Starter</a></li>
<li><a href="https://mp.weixin.qq.com/s/sUtYqosiHjWYj-QPKY0Iew">条件注解,Spring Boot 的基石!</a></li>
</ol>
<h3 id="整合视图层">整合视图层</h3>
<ol>
<li><a href="https://mp.weixin.qq.com/s/7tgiuFceyZPHBZcLnPmkfw">Spring Boot 整合 Thymeleaf</a></li>
<li><a href="https://mp.weixin.qq.com/s/zXwAy1dMlITqHOdBNeZEKg">Spring Boot 整合 Freemarker</a></li>
</ol>
<h3 id="整合-web-开发">整合 Web 开发</h3>
<ol>
<li><a href="https://mp.weixin.qq.com/s/rjscYivhLwg-2ECqps1J-A">Spring Boot 中的静态资源</a></li>
<li><a href="https://mp.weixin.qq.com/s/P-iQ0MH1GLJuO5dNHXEgVw">@ControllerAdvice 注解的三种使用场景!</a></li>
<li><a href="https://mp.weixin.qq.com/s/w26MvCWQ1RO4CUJrfXi5AA">Spring Boot 异常处理方案</a></li>
<li><a href="https://mp.weixin.qq.com/s/ASEJwiswLu1UCRE-e2twYQ">CORS 解决跨域问题</a></li>
<li><a href="https://mp.weixin.qq.com/s/3HFAoAl1OjZ_YnLbQLDF3g">Spring Boot 定义系统启动任务</a></li>
<li><a href="https://mp.weixin.qq.com/s/_20RYBkjKrB4tdpXI3hBOA">Spring Boot 中实现定时任务</a></li>
<li><a href="https://mp.weixin.qq.com/s/iTsTqEeqT9K84S091ycdog">SpringBoot整合Swagger2</a></li>
</ol>
<h3 id="整合持久层技术">整合持久层技术</h3>
<ol>
<li><a href="https://mp.weixin.qq.com/s/X4-e1cf3uZafg8XtMJeo_Q">Spring Boot 整合 JdbcTemplate</a></li>
<li><a href="https://mp.weixin.qq.com/s/7po83-CAoryo1eglumW42Q">Spring Boot 整合 JdbcTemplate 多数据源</a></li>
<li><a href="https://mp.weixin.qq.com/s/HOnX2XRDWrQ9oOKLo1ueKw">SpringBoot 整合 MyBatis</a></li>
<li><a href="https://mp.weixin.qq.com/s/9YXwk2-4zIq60WFuy6nXdw">Spring Boot 整合 MyBatis 多数据源</a></li>
<li><a href="https://mp.weixin.qq.com/s/Fg5ssXuvabZwEfRMKfpY9Q">一文读懂 Spring Data Jpa!</a></li>
</ol>
<blockquote>
<p>Spring Boot 整合 Jpa 的教程欢迎大家在松哥的个人博客(http://www.javaboy.org)上查看,之前发布在公众号上的教程总是被公众号官方判断为有敏感词,但我一直没找到相关敏感词,所以文章总是发送失败。</p>
</blockquote>
<h3 id="整合-nosql">整合 NoSQL</h3>
<ol>
<li><a href="https://mp.weixin.qq.com/s/cgDtmjPWTdh44bSlLC0Qsw">Spring Boot 操作 Redis</a></li>
<li><a href="https://mp.weixin.qq.com/s/ZN07_3ImmyRU0NQaqzcazQ">Nginx 极简入门教程!</a></li>
<li><a href="https://mp.weixin.qq.com/s/xs67SzSkMLz6-HgZVxTDFw">Spring Boot 一个依赖搞定 session 共享</a></li>
</ol>
<h3 id="整合缓存框架">整合缓存框架</h3>
<ol>
<li><a href="https://mp.weixin.qq.com/s/UpTewC66iJyzq0osm_0cfw">Spring Boot + Spring Cache + Redis</a></li>
<li><a href="https://mp.weixin.qq.com/s/i9a3VOf_GMN_UBQ-8tKi3A">Spring Boot + Spring Cache + Ehcache</a></li>
</ol>
<h3 id="构建-rest-服务">构建 REST 服务</h3>
<ol>
<li><a href="https://mp.weixin.qq.com/s/7uO87SOu93XH2Y3iWxWicg">10 行代码构建 RESTful 风格应用</a></li>
</ol>
<h3 id="安全管理">安全管理</h3>
<ol>
<li><a href="https://mp.weixin.qq.com/s/JU_-gn-yZ4VJJXTZvo7nZQ">Spring Boot 整合 Shiro</a></li>
<li><a href="https://mp.weixin.qq.com/s/HKJOlatXDS8awBNyCe9JMg">手把手带你入门 Spring Security!</a></li>
<li><a href="https://mp.weixin.qq.com/s/oDow2miLIst-R4NNzc_i4g">Spring Security 登录添加验证码</a></li>
<li><a href="https://mp.weixin.qq.com/s/X1t-VCxzxIcQKOAu-pJrdw">SpringSecurity 登录使用 JSON 格式数据</a></li>
<li><a href="https://mp.weixin.qq.com/s/7D0qJiEIzNuz8VAVvZsXCA">Spring Security 中的角色继承问题</a></li>
<li><a href="https://mp.weixin.qq.com/s/riyFQSrkQBQBCyomE__fLA">Spring Security 中使用 JWT!</a></li>
</ol>
<h3 id="热部署">热部署</h3>
<ol>
<li><a href="https://mp.weixin.qq.com/s/jS6sy-owtnGitIAf_z894g">LiveReload 使用</a></li>
</ol>
<h3 id="打包">打包</h3>
<ol>
<li><a href="https://mp.weixin.qq.com/s/wTr-8VxAnfB9VUnQO6DDxA">可执行 jar 与普通 jar</a></li>
</ol>
<h3 id="企业开发">企业开发</h3>
<ol>
<li><a href="https://mp.weixin.qq.com/s/8UiEMpono-hUrRVwvDjUgA">Spring Boot 整合邮件发送</a></li>
</ol>
<h3 id="spring-boot-中的-bug">Spring Boot 中的 Bug</h3>
<ol>
<li><a href="https://mp.weixin.qq.com/s/UDwtZgsxsTILPYpBemwJ1g">Spring Boot2.1.5 中的 Bug</a></li>
</ol>
<h3 id="其他资料">其他资料</h3>
<ol>
<li><a href="https://mp.weixin.qq.com/s/deUN4S34y6xwaWnY4Y4jpg">15 个 Spring Boot 高频面试题</a></li>
<li><a href="https://mp.weixin.qq.com/s/L8z4MOfP37fooNpYw-1mQg">八个开源的 Spring Boot 学习资源</a></li>
</ol>
<h2 id="案例">案例</h2>
<p>另外,还有一件重要的事,就是松哥把微信公众号中文章的案例,都整理到 GitHub 上了,<strong>每个案例都对应了一篇解读的文章</strong>,方便大家学习。松哥以前写博客没养成好习惯,有的案例丢失了,现在在慢慢整理补上。</p>
<p>GitHub 仓库地址:<a href="https://github.com/lenve/javaboy-code-samples">https://github.com/lenve/javaboy-code-samples</a>,欢迎大家 star。已有的案例如下图:</p>
<p><img src="http://www.javaboy.org/images/boot/19-1.png" alt="" /></p>
<h2 id="电子书">电子书</h2>
<p>为了方便大家学习,松哥同时整理了一个在线电子书,地址:http://springboot.javaboy.org,如下图:</p>
<p><img src="http://www.javaboy.org/images/boot/32-1.png" alt="" /></p>
<p>在线电子书内容和公众号上面的一样,不过大家在 pc 端打开方便一些。</p>
<p>另外需要强调的是,这个总结不是结束,而是一个新的开始,<code class="language-plaintext highlighter-rouge">Spring Boot2.1.7</code> 8 月 6 号发布,松哥会继续追踪,继续产出最新版的教程,欢迎小伙伴们继续关注。</p>
<p>好了,这就是松哥说的干货,大家撸起袖子加油学吧!如果这个资料帮到你了,欢迎转发或者右下角在看哦。</p>江南一点雨wangsong0210@gmail.com两个月前,松哥总结过一次已经完成的 Spring Boot 教程,当时感受到了小伙伴们巨大的热情。