-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 127 KB
/
content.json
1
{"meta":{"title":"熊峰的博客 - Seifon's Blog","subtitle":"不定期分享一些个人开发经验","description":"Java开发","author":"Seifon","url":"http://www.seifon.cn"},"pages":[{"title":"关于我","date":"2017-09-09T18:11:09.000Z","updated":"2018-07-22T11:51:23.000Z","comments":false,"path":"about/index.html","permalink":"http://www.seifon.cn/about/index.html","excerpt":"","text":"我是一名JAVA开发人员,以后会不定期给大家整理并分享工作当中自己的遇到的一些难题和一些开发经验,希望能够得到大家的支持和肯定,谢谢! GitHub: https://github.com/Seifon Email: [email protected] 微信公众号: QQ交流群:106214222"},{"title":"分类","date":"2017-09-09T18:10:26.000Z","updated":"2018-01-24T15:13:45.000Z","comments":false,"path":"categories/index.html","permalink":"http://www.seifon.cn/categories/index.html","excerpt":"","text":""},{"title":"标签","date":"2017-09-09T18:09:11.000Z","updated":"2018-01-24T15:13:45.000Z","comments":false,"path":"tags/index.html","permalink":"http://www.seifon.cn/tags/index.html","excerpt":"","text":""},{"title":"","date":"2019-01-28T14:39:25.550Z","updated":"2019-01-28T14:39:06.908Z","comments":true,"path":"errno-404.html","permalink":"http://www.seifon.cn/errno-404.html","excerpt":"","text":"404页面 *{margin:0;padding:0;outline:none;font-family:\\5FAE\\8F6F\\96C5\\9ED1,宋体;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-khtml-user-select:none;user-select:none;cursor:default;font-weight:lighter;} .center{margin:0 auto;} .whole{width:100%;height:100%;line-height:100%;position:fixed;bottom:0;left:0;z-index:-1000;overflow:hidden;} .whole img{width:100%;height:100%;} .mask{width:100%;height:100%;position:absolute;top:0;left:0;background:#000;opacity:0.6;filter:alpha(opacity=60);} .b{width:100%;text-align:center;height:400px;position:absolute;top:50%;margin-top:-230px}.a{width:150px;height:50px;margin-top:30px}.a a{display:block;float:left;width:150px;height:50px;background:#fff;text-align:center;line-height:50px;font-size:18px;border-radius:25px;color:#333}.a a:hover{color:#000;box-shadow:#fff 0 0 20px} p{color:#fff;margin-top:40px;font-size:24px;} #num{margin:0 5px;font-weight:bold;} var num=4; function redirect(){ num--; document.getElementById(\"num\").innerHTML=num; if(num 暂时未能找到您查找的页面 可能输入的网址错误或此页面不存在 秒后自动跳转到主页"},{"title":"","date":"2018-12-05T14:57:10.367Z","updated":"2018-07-25T11:36:50.000Z","comments":true,"path":"404.html","permalink":"http://www.seifon.cn/404.html","excerpt":"","text":"404页面 *{margin:0;padding:0;outline:none;font-family:\\5FAE\\8F6F\\96C5\\9ED1,宋体;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-khtml-user-select:none;user-select:none;cursor:default;font-weight:lighter;} .center{margin:0 auto;} .whole{width:100%;height:100%;line-height:100%;position:fixed;bottom:0;left:0;z-index:-1000;overflow:hidden;} .whole img{width:100%;height:100%;} .mask{width:100%;height:100%;position:absolute;top:0;left:0;background:#000;opacity:0.6;filter:alpha(opacity=60);} .b{width:100%;text-align:center;height:400px;position:absolute;top:50%;margin-top:-230px}.a{width:150px;height:50px;margin-top:30px}.a a{display:block;float:left;width:150px;height:50px;background:#fff;text-align:center;line-height:50px;font-size:18px;border-radius:25px;color:#333}.a a:hover{color:#000;box-shadow:#fff 0 0 20px} p{color:#fff;margin-top:40px;font-size:24px;} #num{margin:0 5px;font-weight:bold;} var num=4; function redirect(){ num--; document.getElementById(\"num\").innerHTML=num; if(num 暂时未能找到您查找的页面 可能输入的网址错误或此页面不存在 秒后自动跳转到主页"}],"posts":[{"title":"JVM调优之探索CMS和G1的物理内存归还机制","slug":"JVM调优之探索CMS和G1的物理内存归还机制","date":"2019-08-04T04:37:01.000Z","updated":"2019-08-04T04:37:09.607Z","comments":true,"path":"2019/08/04/JVM调优之探索CMS和G1的物理内存归还机制/","link":"","permalink":"http://www.seifon.cn/2019/08/04/JVM调优之探索CMS和G1的物理内存归还机制/","excerpt":"","text":"前言:公司有一个资产统计系统,使用频率很低,但是要求在使用时查询速度快,因此想到做一些缓存放在内存中,在长时间没有使用,就持久化到磁盘中,并对垃圾进行回收,归还物理内存给操作系统,从而节省宝贵资源给其它业务系统。当我做好缓存时,却发现了一个棘手的问题,通过程序释放资源并通知GC回收资源后,堆内存的已用内存减少了,空闲内存增加了,可是进程占用系统内存却没有减少。查阅了很多资料,也尝试过很多次,都没有完美解决问题。直到后来看到一段评论谈及G1垃圾回收器,才恍然大悟。 接下来,通过一个小demo给大家演示一下两种垃圾回收器对物理内存归还的区别。如果有什么不对的地方,希望大家能够在评论里面指正。 堆大小配置: 1-Xms128M -Xmx2048M 先附上测试代码:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788import org.junit.Test;import java.util.ArrayList;import java.util.List;public class MemoryRecycleTest { @Test public void testMemoryRecycle() throws InterruptedException { List list = new ArrayList(); //指定要生产的对象大小为512m int count = 512; //新建一条线程,负责生产对象 new Thread(() -> { try { for (int i = 1; i <= 10; i++) { System.out.println(String.format("第%s次生产%s大小的对象", i, count)); addObject(list, count); //休眠40秒 Thread.sleep(i * 10000); } } catch (InterruptedException e) { e.printStackTrace(); } }).start(); //新建一条线程,负责清理list,回收jvm内存 new Thread(() -> { for (;;) { //当list内存到达512m,就通知gc回收堆 if (list.size() >= count) { System.out.println("清理list.... 回收jvm内存...."); list.clear(); //通知gc回收 System.gc(); //打印堆内存信息 printJvmMemoryInfo(); } } }).start(); //阻止程序退出 Thread.currentThread().join(); } public void addObject(List list, int count) { for (int i = 0; i < count; i++) { OOMobject ooMobject = new OOMobject(); //向list添加一个1m的对象 list.add(ooMobject); try { //休眠100毫秒 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } public static class OOMobject{ //生成1m的对象 private byte[] bytes=new byte[1024*1024]; } public static void printJvmMemoryInfo() { // 虚拟机级内存情况查询 long vmFree = 0; long vmUse = 0; long vmTotal = 0; long vmMax = 0; int byteToMb = 1024 * 1024; Runtime rt = Runtime.getRuntime(); vmTotal = rt.totalMemory() / byteToMb; vmFree = rt.freeMemory() / byteToMb; vmMax = rt.maxMemory() / byteToMb; vmUse = vmTotal - vmFree; System.out.println(""); System.out.println("JVM内存已用的空间为:" + vmUse + " MB"); System.out.println("JVM内存的空闲空间为:" + vmFree + " MB"); System.out.println("JVM总内存空间为:" + vmTotal + " MB"); System.out.println("JVM总内存最大堆空间为:" + vmMax + " MB"); System.out.println(""); }} 首先使用CMS垃圾回收器: 将jvm运行参数设置为如下: 1-Xms128M -Xmx2048M -XX:+UseConcMarkSweepGC 运行程序后,使用JProfiler查看堆内存情况: 查看控制台打印的内容: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071第1次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:6 MBJVM内存的空闲空间为:936 MBJVM总内存空间为:942 MBJVM总内存最大堆空间为:1990 MB第2次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:4 MBJVM内存的空闲空间为:1025 MBJVM总内存空间为:1029 MBJVM总内存最大堆空间为:1990 MB第3次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:4 MBJVM内存的空闲空间为:680 MBJVM总内存空间为:684 MBJVM总内存最大堆空间为:1990 MB第4次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:4 MBJVM内存的空闲空间为:119 MBJVM总内存空间为:123 MBJVM总内存最大堆空间为:1990 MB第5次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:4 MBJVM内存的空闲空间为:119 MBJVM总内存空间为:123 MBJVM总内存最大堆空间为:1990 MB第6次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:4 MBJVM内存的空闲空间为:119 MBJVM总内存空间为:123 MBJVM总内存最大堆空间为:1990 MB第7次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:4 MBJVM内存的空闲空间为:119 MBJVM总内存空间为:123 MBJVM总内存最大堆空间为:1990 MB第8次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:4 MBJVM内存的空闲空间为:119 MBJVM总内存空间为:123 MBJVM总内存最大堆空间为:1990 MB第9次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:4 MBJVM内存的空闲空间为:119 MBJVM总内存空间为:123 MBJVM总内存最大堆空间为:1990 MB 查看jmap heap 信息: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152C:\\Users>jmap -heap 4716Attaching to process ID 4716, please wait...Debugger attached successfully.Server compiler detected.JVM version is 25.161-b12using parallel threads in the new generation.using thread-local object allocation.Concurrent Mark-Sweep GCHeap Configuration: MinHeapFreeRatio = 40 MaxHeapFreeRatio = 70 MaxHeapSize = 2122317824 (2024.0MB) NewSize = 44695552 (42.625MB) MaxNewSize = 348913664 (332.75MB) OldSize = 89522176 (85.375MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB)Heap Usage:New Generation (Eden + 1 Survivor Space): capacity = 280887296 (267.875MB) used = 1629392 (1.5539093017578125MB) free = 279257904 (266.3210906982422MB) 0.5800874668251284% usedEden Space: capacity = 249692160 (238.125MB) used = 1629392 (1.5539093017578125MB) free = 248062768 (236.5710906982422MB) 0.6525603366961942% usedFrom Space: capacity = 31195136 (29.75MB) used = 0 (0.0MB) free = 31195136 (29.75MB) 0.0% usedTo Space: capacity = 31195136 (29.75MB) used = 0 (0.0MB) free = 31195136 (29.75MB) 0.0% usedconcurrent mark-sweep generation: capacity = 624041984 (595.1328125MB) used = 4169296 (3.9761505126953125MB) free = 619872688 (591.1566619873047MB) 0.6681114583470076% used6718 interned Strings occupying 574968 bytes. 通过统计图和控制台日志,可以看到在运行43秒左右前,使用内存呈直线平滑上升,开辟的内存呈阶梯状上升。当使用内存到达525m时,程序发起了System.gc(),此时垃圾被回收了,因此使用内存回到了10m,可是jvm开辟出来的内存空间却没有归还给操作系统,导致程序一直霸占着960m左右的内存资源。第二次生产对象时,可以看到在运行53秒至1分44秒时,不再开辟新空间,而是重复利用已开辟的内存继续创建对象,当执行第二次System.gc()时,jvm又开辟了一小部分内存,这一次程序霸占了1050m内存资源。第三次生产对象时,可以看到在运行2分05秒至2分55秒时,不再开辟新空间,而是重复利用已开辟的内存继续创建对象,当执行到第三次System.gc()时,jvm归还了一部分内存给操作系统,此时依然霸占着700m内存。……..循环执行10次……从总的情况,可以看出,随着System.gc()次数逐渐增加和时间间隔逐渐拉大,从继续开辟内存变成了慢慢归还内存给了操作系统,直到后面将物理内存全部归还给操作系统。 接下来使用G1垃圾回收器:1-Xms128M -Xmx2048M -XX:+UseG1GC 运行程序后,使用JProfiler查看堆内存情况: 查看控制台打印的内容: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071第1次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:5 MBJVM内存的空闲空间为:123 MBJVM总内存空间为:128 MBJVM总内存最大堆空间为:2024 MB第2次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:4 MBJVM内存的空闲空间为:124 MBJVM总内存空间为:128 MBJVM总内存最大堆空间为:2024 MB第3次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:4 MBJVM内存的空闲空间为:124 MBJVM总内存空间为:128 MBJVM总内存最大堆空间为:2024 MB第4次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:4 MBJVM内存的空闲空间为:124 MBJVM总内存空间为:128 MBJVM总内存最大堆空间为:2024 MB第5次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:4 MBJVM内存的空闲空间为:124 MBJVM总内存空间为:128 MBJVM总内存最大堆空间为:2024 MB第6次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:4 MBJVM内存的空闲空间为:124 MBJVM总内存空间为:128 MBJVM总内存最大堆空间为:2024 MB第7次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:4 MBJVM内存的空闲空间为:124 MBJVM总内存空间为:128 MBJVM总内存最大堆空间为:2024 MB第8次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:4 MBJVM内存的空闲空间为:124 MBJVM总内存空间为:128 MBJVM总内存最大堆空间为:2024 MB第9次生产512大小的对象清理list.... 回收jvm内存....JVM内存已用的空间为:4 MBJVM内存的空闲空间为:124 MBJVM总内存空间为:128 MBJVM总内存最大堆空间为:2024 MB 查看jmap heap 信息: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051C:\\Users>jmap -heap 18112Attaching to process ID 18112, please wait...Debugger attached successfully.Server compiler detected.JVM version is 25.161-b12using thread-local object allocation.Garbage-First (G1) GC with 4 thread(s)Heap Configuration: MinHeapFreeRatio = 40 MaxHeapFreeRatio = 70 MaxHeapSize = 2122317824 (2024.0MB) NewSize = 1363144 (1.2999954223632812MB) MaxNewSize = 1272971264 (1214.0MB) OldSize = 5452592 (5.1999969482421875MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 1048576 (1.0MB)Heap Usage:G1 Heap: regions = 2024 capacity = 2122317824 (2024.0MB) used = 8336616 (7.950416564941406MB) free = 2113981208 (2016.0495834350586MB) 0.39280714253663074% usedG1 Young Generation:Eden Space: regions = 2 capacity = 83886080 (80.0MB) used = 2097152 (2.0MB) free = 81788928 (78.0MB) 2.5% usedSurvivor Space: regions = 0 capacity = 0 (0.0MB) used = 0 (0.0MB) free = 0 (0.0MB) 0.0% usedG1 Old Generation: regions = 11 capacity = 50331648 (48.0MB) used = 6239464 (5.950416564941406MB) free = 44092184 (42.049583435058594MB) 12.396701176961264% used6706 interned Strings occupying 573840 bytes. 通过统计图和控制台日志,可以看到在运行41秒左右前,使用内存呈直线平滑上升,开辟的内存也是呈直线平滑上升。当使用内存到达530m时,程序发起了System.gc(),垃圾被回收,因此使用内存回到了10m。此时会发现神奇的现象出来了,jvm之前开辟出来的剩余内存空间全部归还给了操作系统,内存回到了我们指定的初始jvm堆大小128m。通过多次执行生产对象对比发现,jvm都是在每一次调用System.gc()后全部归还物理内存,不做任何保留。达到了我期望的效果! 总结:CMS垃圾回收器,在内存开辟后,会随着System.gc()执行次数逐渐增多和回收频率逐渐拉长,从继续开辟内存到慢慢归还物理内存给操作系统,直到出现一次全部归还,就会在每次调用System.gc()都归还所有剩余的物理内存给操作系统;G1恰恰相反,G1是在JVM每次回收垃圾后,主动归还物理内存给操作系统,不做任何保留,大大降低了内存占用。 另外,查看java堆栈实时情况,推荐使用JProfiler和VisualVM。如果是本地推荐JProfiler,因为功能强大,不过远程配置麻烦;如果是连远程java进程,推荐VisualVM,功能够用,连接远程只需配置一些jvm参数。 其它说明JDK 12将有G1收集器,将内存返回到操作系统(不调用System.gc)“应用程序空闲时” 12345678jdk9 增加了这个jvm参数:-XX:+ShrinkHeapInSteps使Java堆渐进地缩小到目标大小,该选项默认开启,经过多次GC后堆缩小到目标大小;如果关闭该选项,那么GC后Java堆将立即缩小到目标大小。如果希望最小化Java堆大小,可以关闭改选项,并配合以下选项:-XX:MaxHeapFreeRatio=10 -XX:MinHeapFreeRatio=5这样将保持Java堆空间较小,并减少程序的动态占用空间,这对嵌入式应用非常有用,但对于一般应用,可能降低性能。 参考资料:http://www.imooc.com/wenda/detail/574044https://developer.ibm.com/cn/blog/2017/still-paying-unused-memory-java-app-idle/https://gameinstitute.qq.com/community/detail/118528https://www.zhihu.com/question/30813753https://www.zhihu.com/question/29161424","categories":[],"tags":[],"keywords":[]},{"title":"Feign Stub挡板和Mock","slug":"Feign-Stub挡板和Mock","date":"2019-01-28T14:20:58.000Z","updated":"2019-01-28T14:21:07.023Z","comments":true,"path":"2019/01/28/Feign-Stub挡板和Mock/","link":"","permalink":"http://www.seifon.cn/2019/01/28/Feign-Stub挡板和Mock/","excerpt":"","text":"背景:在项目开发中,会有调用第三方接口的场景。当开发时,对方不愿意提供测试服务器给我们调用,或者有的接口会按调用次数进行计费。当联调时,第三方的测试服务器也可能会出现不稳定,如果他们的服务挂了,我们就一直等着服务恢复,那么这就相当影响效率了。如果我们在开发时,就定义一个挡板或者mock服务,在发起调用时,不直接调到第三方接口,而是调到我们自己的挡板代码或者mock服务,这样就可以避免这些问题了。 优势: 挡板代码,不需要侵入业务代码,可以根据入参做一些动态结果返回 不需要专门开发一个挡板服务,并且在每次启动客户端都先启动挡板服务 可以自由选择使用挡板还是Mock数据 详细Demo代码,已经提交到Github,欢迎star Demo地址: https://github.com/Seifon/FeignStubMock 一、下面我就以一个第三方SMS短信接口来做演示:首先,我们写一个Feign客户端接口,正常调用第三方接口: 1.定义一个SMS短信的Feign客户端接口:1234567891011121314151617181920212223import cn.seifon.example.feignstubmock.dto.YunxunSmsReqDto;import cn.seifon.example.feignstubmock.dto.YunxunSmsRespDto;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;/** * @Author: Seifon * @Description: * @Date: Created in 10:24 2019/1/7 */@FeignClient(name = "smsclient", url = "${sms.url}", primary = false)public interface YunxunSmsFeign { /** * * @param request * @return {"code":"0","failNum":"0","successNum":"1","msgId":"19012516213625881","time":"20190125162136","errorMsg":""} * @return {"code":"107","msgId":"","time":"20190125162358","errorMsg":"手机号码格式错误"} */ @PostMapping("/msg/variable/json") YunxunSmsRespDto send(@RequestBody YunxunSmsReqDto request);} 注意:@FeignClient注解里面的primary属性一定要设置为false,这是为了防止在开启Feign挡板时,出现多个Feign客户端导致启动报错。 2.写一个单元测试:1234567891011121314151617181920212223242526272829303132import cn.seifon.example.feignstubmock.dto.YunxunSmsReqDto;import cn.seifon.example.feignstubmock.dto.YunxunSmsRespDto;import cn.seifon.example.feignstubmock.feign.YunxunSmsFeign;import com.alibaba.fastjson.JSON;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)@SpringBootTestpublic class FeignStubMockApplicationTests { @Autowired private YunxunSmsFeign yunxunSmsFeign; @Test public void feignStubMockTest() { YunxunSmsReqDto yunxunSmsReqDto=new YunxunSmsReqDto(); yunxunSmsReqDto.setAccount("XXXXXXX"); yunxunSmsReqDto.setPassword("XXXXXXX"); yunxunSmsReqDto.setMsg("登录验证码:{$var},请不要对非本人透露。"); yunxunSmsReqDto.setParams("13011112222,123456"); yunxunSmsReqDto.setReport("true"); YunxunSmsRespDto send = yunxunSmsFeign.send(yunxunSmsReqDto); //打印结果 System.out.println(JSON.toJSON(send)); }} 3.1.我们输入一个正确的手机号,拿一个成功的结果:1234567891011121314152019-01-28 11:17:56.718 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] ---> POST http://smssh1.253.com/msg/variable/json HTTP/1.12019-01-28 11:17:56.719 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] Content-Type: application/json;charset=UTF-82019-01-28 11:17:56.720 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] Content-Length: 1602019-01-28 11:17:56.720 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] 2019-01-28 11:17:56.721 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] {"account":"XXXXXX","password":"XXXXXXX","msg":"登录验证码:{$var},请不要对非本人透露。","params":"17311112222,123456","report":"true"}2019-01-28 11:17:56.721 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] ---> END HTTP (160-byte body)2019-01-28 11:17:56.958 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] <--- HTTP/1.1 200 OK (236ms)2019-01-28 11:17:56.960 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] connection: keep-alive2019-01-28 11:17:56.962 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] content-length: 1092019-01-28 11:17:56.963 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] content-type: application/json;charset=UTF-82019-01-28 11:17:56.965 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] date: Mon, 28 Jan 2019 03:17:56 GMT2019-01-28 11:17:56.966 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] 2019-01-28 11:17:56.971 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] {"code":"0","failNum":"0","successNum":"1","msgId":"19012811175621982","time":"20190128111756","errorMsg":""}2019-01-28 11:17:56.972 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] <--- END HTTP (109-byte body){"code":"0","failNum":"0","successNum":"1","msgId":"19012811175621982","time":"20190128111756","errorMsg":""} 此时,我们可以根据日志,看到请求的地址也是第三方的url 3.2.我们输入一个错误的手机号,拿一个失败的结果:1234567891011121314152019-01-28 11:21:15.300 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] ---> POST http://smssh1.253.com/msg/variable/json HTTP/1.12019-01-28 11:21:15.301 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] Content-Type: application/json;charset=UTF-82019-01-28 11:21:15.302 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] Content-Length: 1522019-01-28 11:21:15.302 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] 2019-01-28 11:21:15.303 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] {"account":"XXXXX","password":"XXXXXXX","msg":"登录验证码:{$var},请不要对非本人透露。","params":"173,123456","report":"true"}2019-01-28 11:21:15.303 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] ---> END HTTP (152-byte body)2019-01-28 11:21:15.470 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] <--- HTTP/1.1 200 OK (165ms)2019-01-28 11:21:15.471 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] connection: keep-alive2019-01-28 11:21:15.473 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] content-length: 872019-01-28 11:21:15.474 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] content-type: application/json;charset=UTF-82019-01-28 11:21:15.476 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] date: Mon, 28 Jan 2019 03:21:15 GMT2019-01-28 11:21:15.477 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] 2019-01-28 11:21:15.483 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] {"code":"107","msgId":"","time":"20190128112115","errorMsg":"手机号码格式错误"}2019-01-28 11:21:15.484 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] <--- END HTTP (87-byte body){"code":"107","msgId":"","time":"20190128112115","errorMsg":"手机号码格式错误"} 当我们知道了两种情况下出现的结果,那么我们就可以模拟响应结果啦。小技巧:我们可以先跟对方调接口,把各种响应报文保存下来,方便后面直接mock数据 二、接下来进入挡板编写环节:1.编写一个YunxunSmsFeignStub类,并实现YunxunSmsFeign接口:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748import cn.seifon.example.feignstubmock.dto.YunxunSmsReqDto;import cn.seifon.example.feignstubmock.dto.YunxunSmsRespDto;import cn.seifon.example.feignstubmock.feign.YunxunSmsFeign;import org.apache.commons.lang3.RandomUtils;import org.apache.commons.lang3.StringUtils;import org.apache.commons.lang3.time.DateFormatUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;import org.springframework.context.annotation.Primary;import org.springframework.stereotype.Component;import java.util.Date;/** * @Author: Seifon * @Description: * @Date: Created in 10:24 2019/1/7 */@Primary //注意:需要在原Feign接口@FeignClient注解加入primary = false 属性@Component@ConditionalOnProperty(name = "feign-stub.yunxun.sms.mode", havingValue = "stub")public class YunxunSmsFeignStub implements YunxunSmsFeign { private static final Logger LOG = LoggerFactory.getLogger(YunxunSmsFeignStub.class); @Override public YunxunSmsRespDto send(YunxunSmsReqDto request) { YunxunSmsRespDto yunxunSmsRespDto = new YunxunSmsRespDto(); //模拟正常响应结果 yunxunSmsRespDto.setCode("0"); yunxunSmsRespDto.setFailNum("0"); yunxunSmsRespDto.setSuccessNum("1"); yunxunSmsRespDto.setMsgId(String.valueOf(RandomUtils.nextLong(19000000000000000L, 19999999999999999L))); yunxunSmsRespDto.setTime(DateFormatUtils.format(new Date(), "yyyyMMddHHmmss")); yunxunSmsRespDto.setErrorMsg(""); String params = request.getParams(); String[] paramSplit = StringUtils.split(params, ","); if (paramSplit[0].length() != 11) { //模拟错误响应结果 yunxunSmsRespDto.setCode("107"); yunxunSmsRespDto.setMsgId(""); yunxunSmsRespDto.setErrorMsg("手机号码格式错误"); } return yunxunSmsRespDto; }} 注意:必须标注@Primary注解,否则启动会报错。@ConditionalOnProperty的作用就是根据application.yaml配置的相关属性,判断是否注入Spring容器 2.application.yaml文件,加入下面的配置:12345678sms: url: 'http://smssh1.253.com'#yunxun:代表第三方系统名称,sms:代表业务名称,mode:代表Stub模式,url:代表mock服务地址feign-stub: yunxun: sms: mode: 'stub' 3.为了区分返回的内容是挡板结果,我们可以写一个AOP切面打印日志:12345678910111213141516171819202122232425262728293031323334353637383940import com.alibaba.fastjson.JSON;import org.apache.commons.lang3.StringUtils;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;/** * @Author: Seifon * @Description: * @Date: Created in 10:24 2019/1/7 */@Aspect@Componentpublic class FeignStubAspect { private static final Logger LOG = LoggerFactory.getLogger(FeignStubAspect.class); @Pointcut("execution(* cn.seifon.example.feignstubmock..stub.*.*(..))") public void pointCut(){} @Around("pointCut()") public Object around(ProceedingJoinPoint pjp){ String name = StringUtils.join(pjp.getTarget().getClass().getName(), ".", pjp.getSignature().getName()); LOG.info("-----【{}】---- 进入挡板模式... request: 【{}】", name, JSON.toJSON(pjp.getArgs())); try { Object proceed = pjp.proceed(); LOG.info("-----【{}】---- 退出挡板模式... request: 【{}】, response: 【{}】", name, JSON.toJSON(pjp.getArgs()), JSON.toJSON(proceed)); return proceed; } catch (Throwable e) { e.printStackTrace(); } return null; }} 4.1.运行之前写的单元测试代码(输入一个正确的手机号):1232019-01-28 11:32:51.255 INFO 7488 --- [ main] c.s.e.f.aspect.FeignStubAspect : -----【cn.seifon.example.feignstubmock.feign.stub.YunxunSmsFeignStub.send】---- 进入挡板模式... request: 【[{"msg":"登录验证码:{$var},请不要对非本人透露。","password":"XXXXXXX","report":"true","params":"13011112222,123456","account":"XXXXXXX"}]】2019-01-28 11:32:51.975 INFO 7488 --- [ main] c.s.e.f.aspect.FeignStubAspect : -----【cn.seifon.example.feignstubmock.feign.stub.YunxunSmsFeignStub.send】---- 退出挡板模式... request: 【[{"msg":"登录验证码:{$var},请不要对非本人透露。","password":"XXXXXXX","report":"true","params":"13011112222,123456","account":"XXXXXXX"}]】, response: 【{"code":"0","failNum":"0","successNum":"1","msgId":"19148964234899564","time":"20190128113251","errorMsg":""}】{"code":"0","failNum":"0","successNum":"1","msgId":"19148964234899564","time":"20190128113251","errorMsg":""} 4.2.运行之前写的单元测试代码(输入一个错误的手机号):1232019-01-28 11:35:27.177 INFO 15204 --- [ main] c.s.e.f.aspect.FeignStubAspect : -----【cn.seifon.example.feignstubmock.feign.stub.YunxunSmsFeignStub.send】---- 进入挡板模式... request: 【[{"msg":"登录验证码:{$var},请不要对非本人透露。","password":"XXXXXXX","report":"true","params":"130,123456","account":"XXXXXXX"}]】2019-01-28 11:35:27.900 INFO 15204 --- [ main] c.s.e.f.aspect.FeignStubAspect : -----【cn.seifon.example.feignstubmock.feign.stub.YunxunSmsFeignStub.send】---- 退出挡板模式... request: 【[{"msg":"登录验证码:{$var},请不要对非本人透露。","password":"XXXXXXX","report":"true","params":"130,123456","account":"XXXXXXX"}]】, response: 【{"code":"107","failNum":"0","successNum":"1","msgId":"","time":"20190128113527","errorMsg":"手机号码格式错误"}】{"code":"107","failNum":"0","successNum":"1","msgId":"","time":"20190128113527","errorMsg":"手机号码格式错误"} 以上代码就完成了一个stub挡板功能,可有时候,我们已经拿到第三方接口的返回报文,并切不想去写一大段Stub代码。那么这个时候,我们就可以选择下面的Mock方式去完成我们的功能。 三、接下来进入Mock环节:1. 首先准备一个mock服务,这里我就用自己比较喜欢的一个mock工具(mock-json-server)给大家演示:1.1 安装nodejs:1参看官网:http://nodejs.cn/ 1.2 安装mock-json-server:1npm install -g mock-json-server 1.3 新建mock数据文件(命名为:data.json):123456789101112{ "/msg/variable/json": { "post": { "code":"0", "failNum":"0", "successNum":"1", "msgId":"19012516213625881", "time":"20190125162136", "errorMsg":"" } }} 1.4 运行:123mock-json-server {path}/data.json --port=1240{path}替换为存放data.json的绝对路径 1.5 如果显示如下结果,就代表mock服务运行成功:1JSON Server running at http://localhost:1240/ mock-json-server具体使用文档,请参考:https://www.npmjs.com/package/mock-json-server 2. 准备工作做好后,接下来,就进入Mock正式环节:2.1 首先,我们定义一个YunxunSmsFeignMock接口,并且继承YunxunSmsFeign接口123456789101112131415161718import cn.seifon.example.feignstubmock.feign.YunxunSmsFeign;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.context.annotation.Primary;import org.springframework.stereotype.Component;/** * @Author: Seifon * @Description: * @Date: Created in 10:24 2019/1/7 */@Primary //注意:需要在原Feign接口@FeignClient注解加入primary = false 属性@Component@ConditionalOnProperty(name = "feign-stub.yunxun.sms.mode", havingValue = "mock")@FeignClient(name = "smsclient-mock", url = "${feign-stub.yunxun.sms.mockUrl}" ,path = "/")public interface YunxunSmsFeignMock extends YunxunSmsFeign {} 注意:必须标注@Primary注解,否则启动时会报错。@FeignClient里的name属性不能跟原Feign接口名称相同,如果相同会启动报错。@ConditionalOnProperty的作用就是根据application.yaml配置的相关属性,判断是否注入Spring容器 2.2 application.yaml文件,加入下面的配置:12345678910sms: url: 'http://smssh1.253.com'#生产环境请勿添加此配置。mode说明:''-不开启, 'mock'-mock模式, 'stub'-stub模式。url说明:只有mock模式需要配置调试url。fund为第三方机构,repayment是业务名称#yunxun:代表第三方系统名称,sms:代表业务名称,mode:代表挡板模式,url:代表mock服务地址feign-stub: yunxun: sms: mode: 'mock' mockUrl: "http://localhost:1240" 2.3 为了区分返回的内容是Mock结果,我们可以写一个AOP切面打印日志:12345678910111213141516171819202122232425262728293031323334353637383940import com.alibaba.fastjson.JSON;import org.apache.commons.lang3.StringUtils;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;/** * @Author: Seifon * @Description: * @Date: Created in 10:24 2019/1/7 */@Aspect@Componentpublic class FeignMockAspect { private static final Logger LOG = LoggerFactory.getLogger(FeignMockAspect.class); @Pointcut("execution(* cn.seifon.example.feignstubmock..mock.*.*(..))") public void pointCut(){} @Around("pointCut()") public Object around(ProceedingJoinPoint pjp){ String name = StringUtils.join(pjp.getTarget().getClass().getName(), ".", pjp.getSignature().getName()); LOG.info("-----【{}】---- 进入Mock模式... request: 【{}】", name, JSON.toJSON(pjp.getArgs())); try { Object proceed = pjp.proceed(); LOG.info("-----【{}】---- 退出Mock模式... request: 【{}】, response: 【{}】", name, JSON.toJSON(pjp.getArgs()), JSON.toJSON(proceed)); return proceed; } catch (Throwable e) { e.printStackTrace(); } return null; }} 2.4 运行之前的单元测试类,得到如下结果:1234567891011121314151617181920212019-01-28 16:16:35.567 INFO 8976 --- [ main] c.s.e.f.aspect.FeignMockAspect : -----【com.sun.proxy.$Proxy95.send】---- 进入Mock模式... request: 【[{"msg":"登录验证码:{$var},请不要对非本人透露。","password":"XXXXXXX","report":"true","params":"13011112222,123456","account":"XXXXXXX"}]】2019-01-28 16:16:35.934 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] ---> POST http://localhost:1240/msg/variable/json HTTP/1.12019-01-28 16:16:35.935 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] Content-Type: application/json;charset=UTF-82019-01-28 16:16:35.936 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] Content-Length: 1522019-01-28 16:16:35.936 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] 2019-01-28 16:16:35.937 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] {"account":"XXXXXXX","password":"XXXXXXX","msg":"登录验证码:{$var},请不要对非本人透露。","params":"13011112222,123456","report":"true"}2019-01-28 16:16:35.937 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] ---> END HTTP (152-byte body)2019-01-28 16:16:36.021 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] <--- HTTP/1.1 200 OK (82ms)2019-01-28 16:16:36.021 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] access-control-allow-origin: *2019-01-28 16:16:36.022 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] connection: keep-alive2019-01-28 16:16:36.023 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] content-length: 1092019-01-28 16:16:36.023 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] content-type: application/json; charset=utf-82019-01-28 16:16:36.024 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] date: Mon, 28 Jan 2019 08:16:36 GMT2019-01-28 16:16:36.024 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] etag: W/"6d-XqhLoZB8r6IRF2Lb6CWoIVVNhIQ"2019-01-28 16:16:36.025 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] x-content-type-options: nosniff2019-01-28 16:16:36.026 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] x-powered-by: Express2019-01-28 16:16:36.027 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] 2019-01-28 16:16:36.030 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] {"code":"0","failNum":"0","successNum":"1","msgId":"19012516213625881","time":"20190125162136","errorMsg":""}2019-01-28 16:16:36.030 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] <--- END HTTP (109-byte body)2019-01-28 16:16:36.227 INFO 8976 --- [ main] c.s.e.f.aspect.FeignMockAspect : -----【com.sun.proxy.$Proxy95.send】---- 退出Mock模式... request: 【[{"msg":"登录验证码:{$var},请不要对非本人透露。","password":"XXXXXXX","report":"true","params":"13011112222,123456","account":"XXXXXXX"}]】, response: 【{"code":"0","failNum":"0","successNum":"1","msgId":"19012516213625881","time":"20190125162136","errorMsg":""}】{"code":"0","failNum":"0","successNum":"1","msgId":"19012516213625881","time":"20190125162136","errorMsg":""} 说明:此时我们根据日志,会发现feign调用的url已经变为我们的Mock服务地址了。同理,如果要返回失败结果,只需要修改data.json文件,再次调用后,即可得到我们想要的结果了。 四、结语:如果有什么需要改进的地方,或者不正确的地方,请在评论里面提出并指正。谢谢! 详细Demo代码,已经提交到Github,欢迎star Demo地址: https://github.com/Seifon/FeignStubMock 项目结构,如图:","categories":[],"tags":[],"keywords":[]},{"title":"踩坑 Spring Cloud Hystrix 线程池队列配置","slug":"踩坑-Spring-Cloud-Hystrix-线程池队列配置","date":"2018-12-08T01:50:00.000Z","updated":"2018-12-08T01:52:54.939Z","comments":true,"path":"2018/12/08/踩坑-Spring-Cloud-Hystrix-线程池队列配置/","link":"","permalink":"http://www.seifon.cn/2018/12/08/踩坑-Spring-Cloud-Hystrix-线程池队列配置/","excerpt":"","text":"背景:有一次在生产环境,突然出现了很多笔还款单被挂起,后来排查原因,发现是内部系统调用时出现了Hystrix调用异常。在开发过程中,因为核心线程数设置的比较大,没有出现这种异常。放到了测试环境,偶尔有出现这种情况,后来在网上查找解决方案,网上的方案是调整maxQueueSize属性就好了,当时调整了一下,确实有所改善。可没想到在生产环境跑了一段时间后却又出现这种了情况,此时我第一想法就是去查看maxQueueSize属性,可是maxQueueSize属性是设置值了。当时就比较纳闷了,为什么maxQueueSize属性不起作用,后来通过查看官方文档发现Hystrix还有一个queueSizeRejectionThreshold属性,这个属性是控制队列最大阈值的,而Hystrix默认只配置了5个,因此就算我们把maxQueueSize的值设置再大,也是不起作用的。两个属性必须同时配置 先看一下正确的Hystrix配置姿势。application.yml: 123456hystrix: threadpool: default: coreSize: 200 #并发执行的最大线程数,默认10 maxQueueSize: 1000 #BlockingQueue的最大队列数,默认值-1 queueSizeRejectionThreshold: 800 #即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝,默认值5 接下来编写一个测试类,来验证几种错误配置,看看会出现什么情况。测试类代码(A调用方):12345678910111213141516171819202122232425262728293031323334353637383940414243444546/** * @Author: XiongFeng * @Description: * @Date: Created in 11:12 2018/6/11 */public class RepaymentHelperTest extends FundApplicationTests { @Autowired RepaymentHelper repaymentHelper; @Autowired private RouterFeign routerFeign; @Test public void hystrixTest() throws InterruptedException { for (int i = 0; i < 135; i++) { new Thread(new Runnable() { @Override public void run() { job(); } }).start(); } Thread.currentThread().join(); } public void job() { String repaymentNo = "xf1002"; String transNo = "T4324324234"; String reqNo = "xf1002"; String begintime = "20180831130030"; String endtime = "20180831130050"; TransRecQueryReqDto transRecQueryReqDto = new TransRecQueryReqDto(); transRecQueryReqDto.setTransNo(transNo); transRecQueryReqDto.setBeginTime(begintime); transRecQueryReqDto.setEndTime(endtime); transRecQueryReqDto.setReqNo(reqNo); Resp<List<TransRecDto>> queryTransRecListResp = routerFeign.queryTransRec(new Req<>(repaymentNo, "2018080200000002", null, null, transRecQueryReqDto)); System.out.println(String.format("获取结果为:【%s】", JsonUtil.toJson(queryTransRecListResp))); }} 这个测试类的作用就是创建135个线程,通过RouterFeign类并发请求B服务方,看看请求结果是否出现异常。 Feign调用代码:12345678910111213@FeignClient(value = "${core.name}", fallbackFactory = RouterFeignBackFactory.class, path = "/router")public interface RouterFeign { /** * 代扣结果查询 * @param transRecQueryReqDtoReq * @return */ @PostMapping("/queryTransRec") Resp<List<TransRecDto>> queryTransRec(@RequestBody Req<TransRecQueryReqDto> transRecQueryReqDtoReq);} 这个类,就是通过Feign方式去调用B服务方的客户端 服务提供方代码(B服务方):1234567891011121314151617181920212223/** * @Author: XiongFeng * @Description: * @Date: Created in 16:04 2018/5/24 */@Api("还款服务")@RefreshScope@RestController@RequestMapping("/router")public class TestController { private static Logger logger = LoggerFactory.getLogger(TestController.class); // 计数器 private static AtomicInteger count = new AtomicInteger(1); @ApiOperation(value = "代扣结果查询") @PostMapping("/queryTransRec") Resp<List<TransRecDto>> queryTransRec(@RequestBody Req<TransRecQueryReqDto> transRecQueryReqDtoReq) throws InterruptedException { System.out.println(String.format("查询支付结果......计数: %s", count.getAndAdd(1))); Thread.sleep(500); return Resp.success(RespStatus.SUCCESS.getDesc(), null); } 这个类的作用,就是一个服务提供方,计数并返回结果。 下面我们看一下几种错误的配置。案例一(将核心线程数调低,最大队列数调大一点,但是队列拒绝阈值设置小一点):123456hystrix: threadpool: default: coreSize: 10 maxQueueSize: 1000 queueSizeRejectionThreshold: 20 此时的结果: 左窗口是B服务方,右窗口是A调用方。从结果可以看出,调用135次,成功32次左右,其余线程全部抛异常。 案例二(将核心线程数调低,最大队列数调小一点,但是队列拒绝阈值设置大一点):123456hystrix: threadpool: default: coreSize: 10 maxQueueSize: 15 queueSizeRejectionThreshold: 2000 此时的结果:1java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@7d6d472b rejected from java.util.concurrent.ThreadPoolExecutor@17f8bcb7[Running, pool size = 3, active threads = 3, queued tasks = 15, completed tasks = 0] 左窗口是B服务方,右窗口是A调用方。从结果可以看出,调用135次,成功25次左右,其余线程全部抛异常。。 案例三(将核心线程数调低,最大队列数调大一点,但是队列拒绝阈值不设置值):12345hystrix: threadpool: default: coreSize: 10 maxQueueSize: 1500 此时的结果:1java.util.concurrent.RejectedExecutionException: Rejected command because thread-pool queueSize is at rejection threshold. 左窗口是B服务方,右窗口是A调用方。此时的结果和案例一的情况一样,调用135次,成功47次左右,其余线程全部抛异常。报错跟案例一一样 案例四(将核心线程数调低,最大队列数不设值,但是队列拒绝阈值设置的比较大):12345hystrix: threadpool: default: coreSize: 10 queueSizeRejectionThreshold: 1000 此时的结果:12345java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@23d268ea rejected from java.util.concurrent.ThreadPoolExecutor@66d0e2f4[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0] at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379) at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112) 左窗口是B服务方,右窗口是A调用方。此时的结果和案例二的情况一样,调用135次,成功10次左右,其余线程全部抛异常。报错跟案例二一样 下面来看一看正确的配置案例案例一:将核心线程数调低,最大队列数和队列拒绝阈值的值都设置大一点):123456hystrix: threadpool: default: coreSize: 10 maxQueueSize: 1500 queueSizeRejectionThreshold: 1000 此时的结果: 左窗口是B服务方,右窗口是A调用方。此时的结果就完全正常了,并发请求了135次,全部成功! 结论:官方默认队列阈值只有5个, 如果要调整队列,必须同时修改maxQueueSize和queueSizeRejectionThreshold属性的值,否则都会出现异常!参考文档:Spring Hystrix 官方文档","categories":[{"name":"Spring","slug":"Spring","permalink":"http://www.seifon.cn/categories/Spring/"}],"tags":[{"name":"Hystrix","slug":"Hystrix","permalink":"http://www.seifon.cn/tags/Hystrix/"},{"name":"Spring Cloud","slug":"Spring-Cloud","permalink":"http://www.seifon.cn/tags/Spring-Cloud/"},{"name":"多线程","slug":"多线程","permalink":"http://www.seifon.cn/tags/多线程/"}],"keywords":[{"name":"Spring","slug":"Spring","permalink":"http://www.seifon.cn/categories/Spring/"}]},{"title":"Spring统一返回Json工具类,带分页信息","slug":"Spring统一返回Json工具类,带分页信息","date":"2018-04-30T12:37:00.000Z","updated":"2019-06-16T16:01:02.210Z","comments":true,"path":"2018/04/30/Spring统一返回Json工具类,带分页信息/","link":"","permalink":"http://www.seifon.cn/2018/04/30/Spring统一返回Json工具类,带分页信息/","excerpt":"","text":"前言: 项目做前后端分离时,我们会经常提供Json数据给前端,如果有一个统一的Json格式返回工具类,那么将大大提高开发效率和减低沟通成本。 此Json响应工具类,支持带分页信息,支持泛型,支持HttpStatus标准返回码 效果预览: 步入正题:1. Resp工具类123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249import java.io.Serializable;import java.util.Map;/** * @Author: XiongFeng * @Description: Restful统一Json响应对象封装 * @Date: Created in 9:39 2018/4/10 */public class Resp<T> implements Serializable { private static final long serialVersionUID = 1L; private final static String SUCCESS_CODE = "200"; /** * 返回状态码 */ private String status; /** * 返回消息 */ private String message; /** * 返回内容 */ private T data; /** * 分页信息 */ private PageInfo page; /** * 其他内容 */ private Map<String, Object> ext; public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public T getData() { return data; } public void setData(T data) { this.data = data; } public Map<String, Object> getExt() { return ext; } public void setExt(Map<String, Object> ext) { this.ext = ext; } public PageInfo getPage() { return page; } public void setPage(PageInfo page) { this.page = page; } public Resp(){ this.status = SUCCESS_CODE; this.message = "SUCCESS"; } public Resp(String status, String message) { this.status = status; this.message = message; } public Resp(String status, String message, T data) { this.status = status; this.message = message; this.data = data; } public Resp(String status, String message, T data, Map<String, Object> ext) { this.status = status; this.message = message; this.data = data; this.ext = ext; } public Resp(String status, String message, T data, PageInfo pageInfo) { this.status = status; this.message = message; this.data = data; this.page = pageInfo; } public Resp(String status, String message, T data, Map<String, Object> ext, PageInfo pageInfo) { this.status = status; this.message = message; this.data = data; this.ext = ext; this.page = pageInfo; } public Resp(String status, String message, T data, Long total, Integer pageNo, Integer pageSize){ PageInfo pageInfo = new PageInfo(total, pageNo, pageSize); this.status = status; this.message = message; this.data = data; this.page = pageInfo; } public Resp(String status, String message, T data, Map<String, Object> ext, Long total, Integer pageNo, Integer pageSize){ PageInfo pageInfo = new PageInfo(total, pageNo, pageSize); this.status = status; this.message = message; this.data = data; this.ext = ext; this.page = pageInfo; } //快速返回成功 public static <T>Resp success(){ return new Resp<T>(SUCCESS_CODE,"请求成功",null); } public static <T>Resp success(T result){ return new Resp<T>(SUCCESS_CODE,"请求成功",result); } public static <T>Resp success(String message, T result){ return new Resp<T>(SUCCESS_CODE,message,result); } public static <T>Resp success(String message, T result, Map<String, Object> extra){ return new Resp<T>(SUCCESS_CODE,message,result, extra); } public static <T>Resp success(T result, Long total, Integer pageNo, Integer pageSize){ PageInfo pageInfo = new PageInfo(total, pageNo, pageSize); return new Resp<T>(SUCCESS_CODE,"请求成功",result, pageInfo); } public static <T>Resp success(T result, Map<String, Object> extra, Long total, Integer pageNo, Integer pageSize){ PageInfo pageInfo = new PageInfo(total, pageNo, pageSize); return new Resp<T>(SUCCESS_CODE,"请求成功",result, extra,pageInfo); } public static <T>Resp success(String message, T result, Long total, Integer pageNo, Integer pageSize){ PageInfo pageInfo = new PageInfo(total, pageNo, pageSize); return new Resp<T>(SUCCESS_CODE,message,result,pageInfo); } public static <T>Resp success(String message, T result, Map<String, Object> extra, Long total, Integer pageNo, Integer pageSize){ PageInfo pageInfo = new PageInfo(total, pageNo, pageSize); return new Resp<T>(SUCCESS_CODE,message,result, extra,pageInfo); } //快速返回失败状态 public static <T>Resp fail(){ return new Resp<T>(ErrorCode.SYSTEM_ERROR.getCode(),ErrorCode.SYSTEM_ERROR.getMessage()); } public static <T>Resp fail(T result){ return new Resp<T>(ErrorCode.SYSTEM_ERROR.getCode(),ErrorCode.SYSTEM_ERROR.getMessage(),result); } public <T>Resp fail(String message, T result){ return new Resp<T>(ErrorCode.SYSTEM_ERROR.getCode(),message,result); } public <T>Resp fail(String message, T result, Map<String, Object> extra){ return new Resp<T>(ErrorCode.SYSTEM_ERROR.getCode(),message,result, extra); } public static <T>Resp fail(ErrorCode errorCode){ return new Resp<T>(errorCode.getCode(),errorCode.getMessage()); } public static <T>Resp fail(ErrorCode errorCode, T result){ return new Resp<T>(errorCode.getCode(),errorCode.getMessage(),result); } public static <T>Resp fail(ErrorCode errorCode, String message, T result){ return new Resp<T>(errorCode.getCode(),message,result); } public static <T>Resp fail(ErrorCode errorCode, String message, T result, Map<String, Object> extra){ return new Resp<T>(errorCode.getCode(),message,result, extra); } //快速返回自定义状态码 public static <T>Resp result(String statusCode, String message){ return new Resp<T>(statusCode,message); } public static <T>Resp result(String statusCode, String message, T result){ return new Resp<T>(statusCode,message,result); } public static <T>Resp result(String statusCode, String message, T result, Map<String, Object> extra){ return new Resp<T>(statusCode,message,result, extra); } public static <T>Resp result(String statusCode, String message, T result, Long total, Integer pageNo, Integer pageSize){ PageInfo pageInfo = new PageInfo(total, pageNo, pageSize); return new Resp<T>(statusCode,message,result, pageInfo); } public static <T>Resp result(String statusCode, String message, T result, Map<String, Object> extra, Long total, Integer pageNo, Integer pageSize){ PageInfo pageInfo = new PageInfo(total, pageNo, pageSize); return new Resp<T>(statusCode,message,result, extra,pageInfo); } //快速返回Http状态 public static <T>Resp httpStatus(HttpStatus httpStatus, String message){ return result(httpStatus.toString(),message); } public static <T>Resp httpStatus(HttpStatus httpStatus, String message, T result){ return result(httpStatus.toString(),message,result); } public static <T>Resp httpStatus(HttpStatus httpStatus, String message, T result, Map<String, Object> extra){ return result(httpStatus.toString(),message,result, extra); } public static <T>Resp httpStatus(HttpStatus httpStatus, String message, T result, Long total, Integer pageNo, Integer pageSize){ PageInfo pageInfo = new PageInfo(total, pageNo, pageSize); return result(httpStatus.toString(),message,result, total, pageNo, pageSize); } public static <T>Resp httpStatus(HttpStatus httpStatus, String message, T result, Map<String, Object> extra, Long total, Integer pageNo, Integer pageSize){ PageInfo pageInfo = new PageInfo(total, pageNo, pageSize); return result(httpStatus.toString(),message,result, extra, total, pageNo, pageSize); }} 2. PageInfo 用于封装分页信息的123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081import java.io.Serializable;/** * @Author: XiongFeng * @Description: 分页信息 * @Date: Created in 9:39 2018/4/10 */public class PageInfo implements Serializable{ private static final long serialVersionUID = 1L; /** * 总记录数 */ private Long total; /** * 总页数 */ private Long totalPages; /** * 当前页 */ private Integer pageNo; /** * 页记录数 */ private Integer pageSize; public PageInfo(Long total) { this.total = total; } public PageInfo(Long total, Integer pageNo, Integer pageSize) { this.total = total; this.totalPages = (total - 1) / pageSize + 1; this.pageNo = pageNo; this.pageSize = pageSize; } public static PageInfo page(Long total) { return new PageInfo(total); } public static PageInfo page( Long total, Integer pageNo, Integer pageSize) { return new PageInfo(total, pageNo, pageSize); } public Long getTotal() { return total; } public void setTotal(Long total) { this.total = total; } public Long getTotalPages() { return totalPages; } public void setTotalPages(Long totalPages) { this.totalPages = totalPages; } public Integer getPageNo() { return pageNo; } public void setPageNo(Integer pageNo) { this.pageNo = pageNo; } public Integer getPageSize() { return pageSize; } public void setPageSize(Integer pageSize) { this.pageSize = pageSize; }} 4. ErrorCode 用于定义错误码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748/** * @Author: XiongFeng * @Description: 错误码 * @Date: Created in 9:39 2018/4/10 */public enum ErrorCode { SYSTEM_ERROR(500, "系统错误"), PARAMETER_CHECK_ERROR(400, "参数校验错误"), AUTH_VALID_ERROR(701, "用户权限不足"), UNLOGIN_ERROR(401, "用户未登录或登录状态超时失效"), ; private final Integer value; private final String message; ErrorCode(int value, String message) { this.value = value; this.message = message; } public int getValue() { return value; } public String getMessage() { return message; } @Override public String toString() { return value.toString(); } public String getCode() { return value.toString(); } public static ErrorCode getByCode(Integer value) { for (ErrorCode _enum : values()) { if (_enum.getValue() == value) { return _enum; } } return null; }} 5. HttpStatus 提取自org.springframework.http,可以自己引入123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523/** * Enumeration of HTTP status codes. * * <p>The HTTP status code series can be retrieved via {@link #series()}. * * @author Arjen Poutsma * @author Sebastien Deleuze * @author Brian Clozel * @since 3.0 * @see HttpStatus.Series * @see <a href="http://www.iana.org/assignments/http-status-codes">HTTP Status Code Registry</a> * @see <a href="http://en.wikipedia.org/wiki/List_of_HTTP_status_codes">List of HTTP status codes - Wikipedia</a> */public enum HttpStatus { // 1xx Informational /** * {@code 100 Continue}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.2.1">HTTP/1.1: Semantics and Content, section 6.2.1</a> */ CONTINUE(100, "Continue"), /** * {@code 101 Switching Protocols}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.2.2">HTTP/1.1: Semantics and Content, section 6.2.2</a> */ SWITCHING_PROTOCOLS(101, "Switching Protocols"), /** * {@code 102 Processing}. * @see <a href="http://tools.ietf.org/html/rfc2518#section-10.1">WebDAV</a> */ PROCESSING(102, "Processing"), /** * {@code 103 Checkpoint}. * @see <a href="http://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal">A proposal for supporting * resumable POST/PUT HTTP requests in HTTP/1.0</a> */ CHECKPOINT(103, "Checkpoint"), // 2xx Success /** * {@code 200 OK}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.3.1">HTTP/1.1: Semantics and Content, section 6.3.1</a> */ OK(200, "OK"), /** * {@code 201 Created}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.3.2">HTTP/1.1: Semantics and Content, section 6.3.2</a> */ CREATED(201, "Created"), /** * {@code 202 Accepted}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.3.3">HTTP/1.1: Semantics and Content, section 6.3.3</a> */ ACCEPTED(202, "Accepted"), /** * {@code 203 Non-Authoritative Information}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.3.4">HTTP/1.1: Semantics and Content, section 6.3.4</a> */ NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"), /** * {@code 204 No Content}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.3.5">HTTP/1.1: Semantics and Content, section 6.3.5</a> */ NO_CONTENT(204, "No Content"), /** * {@code 205 Reset Content}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.3.6">HTTP/1.1: Semantics and Content, section 6.3.6</a> */ RESET_CONTENT(205, "Reset Content"), /** * {@code 206 Partial Content}. * @see <a href="http://tools.ietf.org/html/rfc7233#section-4.1">HTTP/1.1: Range Requests, section 4.1</a> */ PARTIAL_CONTENT(206, "Partial Content"), /** * {@code 207 Multi-Status}. * @see <a href="http://tools.ietf.org/html/rfc4918#section-13">WebDAV</a> */ MULTI_STATUS(207, "Multi-Status"), /** * {@code 208 Already Reported}. * @see <a href="http://tools.ietf.org/html/rfc5842#section-7.1">WebDAV Binding Extensions</a> */ ALREADY_REPORTED(208, "Already Reported"), /** * {@code 226 IM Used}. * @see <a href="http://tools.ietf.org/html/rfc3229#section-10.4.1">Delta encoding in HTTP</a> */ IM_USED(226, "IM Used"), // 3xx Redirection /** * {@code 300 Multiple Choices}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.4.1">HTTP/1.1: Semantics and Content, section 6.4.1</a> */ MULTIPLE_CHOICES(300, "Multiple Choices"), /** * {@code 301 Moved Permanently}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.4.2">HTTP/1.1: Semantics and Content, section 6.4.2</a> */ MOVED_PERMANENTLY(301, "Moved Permanently"), /** * {@code 302 Found}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.4.3">HTTP/1.1: Semantics and Content, section 6.4.3</a> */ FOUND(302, "Found"), /** * {@code 302 Moved Temporarily}. * @see <a href="http://tools.ietf.org/html/rfc1945#section-9.3">HTTP/1.0, section 9.3</a> * @deprecated in favor of {@link #FOUND} which will be returned from {@code HttpStatus.valueOf(302)} */ @Deprecated MOVED_TEMPORARILY(302, "Moved Temporarily"), /** * {@code 303 See Other}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.4.4">HTTP/1.1: Semantics and Content, section 6.4.4</a> */ SEE_OTHER(303, "See Other"), /** * {@code 304 Not Modified}. * @see <a href="http://tools.ietf.org/html/rfc7232#section-4.1">HTTP/1.1: Conditional Requests, section 4.1</a> */ NOT_MODIFIED(304, "Not Modified"), /** * {@code 305 Use Proxy}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.4.5">HTTP/1.1: Semantics and Content, section 6.4.5</a> * @deprecated due to security concerns regarding in-band configuration of a proxy */ @Deprecated USE_PROXY(305, "Use Proxy"), /** * {@code 307 Temporary Redirect}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.4.7">HTTP/1.1: Semantics and Content, section 6.4.7</a> */ TEMPORARY_REDIRECT(307, "Temporary Redirect"), /** * {@code 308 Permanent Redirect}. * @see <a href="http://tools.ietf.org/html/rfc7238">RFC 7238</a> */ PERMANENT_REDIRECT(308, "Permanent Redirect"), // --- 4xx Client Error --- /** * {@code 400 Bad Request}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.1">HTTP/1.1: Semantics and Content, section 6.5.1</a> */ BAD_REQUEST(400, "Bad Request"), /** * {@code 401 Unauthorized}. * @see <a href="http://tools.ietf.org/html/rfc7235#section-3.1">HTTP/1.1: Authentication, section 3.1</a> */ UNAUTHORIZED(401, "Unauthorized"), /** * {@code 402 Payment Required}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.2">HTTP/1.1: Semantics and Content, section 6.5.2</a> */ PAYMENT_REQUIRED(402, "Payment Required"), /** * {@code 403 Forbidden}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.3">HTTP/1.1: Semantics and Content, section 6.5.3</a> */ FORBIDDEN(403, "Forbidden"), /** * {@code 404 Not Found}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.4">HTTP/1.1: Semantics and Content, section 6.5.4</a> */ NOT_FOUND(404, "Not Found"), /** * {@code 405 Method Not Allowed}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.5">HTTP/1.1: Semantics and Content, section 6.5.5</a> */ METHOD_NOT_ALLOWED(405, "Method Not Allowed"), /** * {@code 406 Not Acceptable}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.6">HTTP/1.1: Semantics and Content, section 6.5.6</a> */ NOT_ACCEPTABLE(406, "Not Acceptable"), /** * {@code 407 Proxy Authentication Required}. * @see <a href="http://tools.ietf.org/html/rfc7235#section-3.2">HTTP/1.1: Authentication, section 3.2</a> */ PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"), /** * {@code 408 Request Timeout}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.7">HTTP/1.1: Semantics and Content, section 6.5.7</a> */ REQUEST_TIMEOUT(408, "Request Timeout"), /** * {@code 409 Conflict}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.8">HTTP/1.1: Semantics and Content, section 6.5.8</a> */ CONFLICT(409, "Conflict"), /** * {@code 410 Gone}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.9">HTTP/1.1: Semantics and Content, section 6.5.9</a> */ GONE(410, "Gone"), /** * {@code 411 Length Required}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.10">HTTP/1.1: Semantics and Content, section 6.5.10</a> */ LENGTH_REQUIRED(411, "Length Required"), /** * {@code 412 Precondition failed}. * @see <a href="http://tools.ietf.org/html/rfc7232#section-4.2">HTTP/1.1: Conditional Requests, section 4.2</a> */ PRECONDITION_FAILED(412, "Precondition Failed"), /** * {@code 413 Payload Too Large}. * @since 4.1 * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.11">HTTP/1.1: Semantics and Content, section 6.5.11</a> */ PAYLOAD_TOO_LARGE(413, "Payload Too Large"), /** * {@code 413 Request Entity Too Large}. * @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.14">HTTP/1.1, section 10.4.14</a> * @deprecated in favor of {@link #PAYLOAD_TOO_LARGE} which will be returned from {@code HttpStatus.valueOf(413)} */ @Deprecated REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large"), /** * {@code 414 URI Too Long}. * @since 4.1 * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.12">HTTP/1.1: Semantics and Content, section 6.5.12</a> */ URI_TOO_LONG(414, "URI Too Long"), /** * {@code 414 Request-URI Too Long}. * @see <a href="http://tools.ietf.org/html/rfc2616#section-10.4.15">HTTP/1.1, section 10.4.15</a> * @deprecated in favor of {@link #URI_TOO_LONG} which will be returned from {@code HttpStatus.valueOf(414)} */ @Deprecated REQUEST_URI_TOO_LONG(414, "Request-URI Too Long"), /** * {@code 415 Unsupported Media Type}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.13">HTTP/1.1: Semantics and Content, section 6.5.13</a> */ UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"), /** * {@code 416 Requested Range Not Satisfiable}. * @see <a href="http://tools.ietf.org/html/rfc7233#section-4.4">HTTP/1.1: Range Requests, section 4.4</a> */ REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested range not satisfiable"), /** * {@code 417 Expectation Failed}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.14">HTTP/1.1: Semantics and Content, section 6.5.14</a> */ EXPECTATION_FAILED(417, "Expectation Failed"), /** * {@code 418 I'm a teapot}. * @see <a href="http://tools.ietf.org/html/rfc2324#section-2.3.2">HTCPCP/1.0</a> */ I_AM_A_TEAPOT(418, "I'm a teapot"), /** * @deprecated See <a href="http://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt">WebDAV Draft Changes</a> */ @Deprecated INSUFFICIENT_SPACE_ON_RESOURCE(419, "Insufficient Space On Resource"), /** * @deprecated See <a href="http://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt">WebDAV Draft Changes</a> */ @Deprecated METHOD_FAILURE(420, "Method Failure"), /** * @deprecated See <a href="http://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt">WebDAV Draft Changes</a> */ @Deprecated DESTINATION_LOCKED(421, "Destination Locked"), /** * {@code 422 Unprocessable Entity}. * @see <a href="http://tools.ietf.org/html/rfc4918#section-11.2">WebDAV</a> */ UNPROCESSABLE_ENTITY(422, "Unprocessable Entity"), /** * {@code 423 Locked}. * @see <a href="http://tools.ietf.org/html/rfc4918#section-11.3">WebDAV</a> */ LOCKED(423, "Locked"), /** * {@code 424 Failed Dependency}. * @see <a href="http://tools.ietf.org/html/rfc4918#section-11.4">WebDAV</a> */ FAILED_DEPENDENCY(424, "Failed Dependency"), /** * {@code 426 Upgrade Required}. * @see <a href="http://tools.ietf.org/html/rfc2817#section-6">Upgrading to TLS Within HTTP/1.1</a> */ UPGRADE_REQUIRED(426, "Upgrade Required"), /** * {@code 428 Precondition Required}. * @see <a href="http://tools.ietf.org/html/rfc6585#section-3">Additional HTTP Status Codes</a> */ PRECONDITION_REQUIRED(428, "Precondition Required"), /** * {@code 429 Too Many Requests}. * @see <a href="http://tools.ietf.org/html/rfc6585#section-4">Additional HTTP Status Codes</a> */ TOO_MANY_REQUESTS(429, "Too Many Requests"), /** * {@code 431 Request Header Fields Too Large}. * @see <a href="http://tools.ietf.org/html/rfc6585#section-5">Additional HTTP Status Codes</a> */ REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large"), /** * {@code 451 Unavailable For Legal Reasons}. * @see <a href="https://tools.ietf.org/html/draft-ietf-httpbis-legally-restricted-status-04"> * An HTTP Status Code to Report Legal Obstacles</a> * @since 4.3 */ UNAVAILABLE_FOR_LEGAL_REASONS(451, "Unavailable For Legal Reasons"), // --- 5xx Server Error --- /** * {@code 500 Internal Server Error}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.6.1">HTTP/1.1: Semantics and Content, section 6.6.1</a> */ INTERNAL_SERVER_ERROR(500, "Internal Server Error"), /** * {@code 501 Not Implemented}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.6.2">HTTP/1.1: Semantics and Content, section 6.6.2</a> */ NOT_IMPLEMENTED(501, "Not Implemented"), /** * {@code 502 Bad Gateway}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.6.3">HTTP/1.1: Semantics and Content, section 6.6.3</a> */ BAD_GATEWAY(502, "Bad Gateway"), /** * {@code 503 Service Unavailable}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.6.4">HTTP/1.1: Semantics and Content, section 6.6.4</a> */ SERVICE_UNAVAILABLE(503, "Service Unavailable"), /** * {@code 504 Gateway Timeout}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.6.5">HTTP/1.1: Semantics and Content, section 6.6.5</a> */ GATEWAY_TIMEOUT(504, "Gateway Timeout"), /** * {@code 505 HTTP Version Not Supported}. * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.6.6">HTTP/1.1: Semantics and Content, section 6.6.6</a> */ HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version not supported"), /** * {@code 506 Variant Also Negotiates} * @see <a href="http://tools.ietf.org/html/rfc2295#section-8.1">Transparent Content Negotiation</a> */ VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates"), /** * {@code 507 Insufficient Storage} * @see <a href="http://tools.ietf.org/html/rfc4918#section-11.5">WebDAV</a> */ INSUFFICIENT_STORAGE(507, "Insufficient Storage"), /** * {@code 508 Loop Detected} * @see <a href="http://tools.ietf.org/html/rfc5842#section-7.2">WebDAV Binding Extensions</a> */ LOOP_DETECTED(508, "Loop Detected"), /** * {@code 509 Bandwidth Limit Exceeded} */ BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded"), /** * {@code 510 Not Extended} * @see <a href="http://tools.ietf.org/html/rfc2774#section-7">HTTP Extension Framework</a> */ NOT_EXTENDED(510, "Not Extended"), /** * {@code 511 Network Authentication Required}. * @see <a href="http://tools.ietf.org/html/rfc6585#section-6">Additional HTTP Status Codes</a> */ NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required"); private final int value; private final String reasonPhrase; HttpStatus(int value, String reasonPhrase) { this.value = value; this.reasonPhrase = reasonPhrase; } /** * Return the integer value of this status code. */ public int value() { return this.value; } /** * Return the reason phrase of this status code. */ public String getReasonPhrase() { return this.reasonPhrase; } /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#INFORMATIONAL}. * This is a shortcut for checking the value of {@link #series()}. */ public boolean is1xxInformational() { return Series.INFORMATIONAL.equals(series()); } /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#SUCCESSFUL}. * This is a shortcut for checking the value of {@link #series()}. */ public boolean is2xxSuccessful() { return Series.SUCCESSFUL.equals(series()); } /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#REDIRECTION}. * This is a shortcut for checking the value of {@link #series()}. */ public boolean is3xxRedirection() { return Series.REDIRECTION.equals(series()); } /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR}. * This is a shortcut for checking the value of {@link #series()}. */ public boolean is4xxClientError() { return Series.CLIENT_ERROR.equals(series()); } /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#SERVER_ERROR}. * This is a shortcut for checking the value of {@link #series()}. */ public boolean is5xxServerError() { return Series.SERVER_ERROR.equals(series()); } /** * Returns the HTTP status series of this status code. * @see HttpStatus.Series */ public Series series() { return Series.valueOf(this); } /** * Return a string representation of this status code. */ @Override public String toString() { return Integer.toString(this.value); } /** * Return the enum constant of this type with the specified numeric value. * @param statusCode the numeric value of the enum to be returned * @return the enum constant with the specified numeric value * @throws IllegalArgumentException if this enum has no constant for the specified numeric value */ public static HttpStatus valueOf(int statusCode) { for (HttpStatus status : values()) { if (status.value == statusCode) { return status; } } throw new IllegalArgumentException("No matching constant for [" + statusCode + "]"); } /** * Enumeration of HTTP status series. * <p>Retrievable via {@link HttpStatus#series()}. */ public enum Series { INFORMATIONAL(1), SUCCESSFUL(2), REDIRECTION(3), CLIENT_ERROR(4), SERVER_ERROR(5); private final int value; Series(int value) { this.value = value; } /** * Return the integer value of this status series. Ranges from 1 to 5. */ public int value() { return this.value; } public static Series valueOf(int status) { int seriesCode = status / 100; for (Series series : values()) { if (series.value == seriesCode) { return series; } } throw new IllegalArgumentException("No matching constant for [" + status + "]"); } public static Series valueOf(HttpStatus status) { return valueOf(status.value); } }} 6. 测试示例:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950@ResponseBody@RequestMapping("/json")public Resp json(String token) { MemberDetails member = memberService.getMemberDetailsByToken(token); return Resp.success(member, 5L, 1, 3); //这里分页信息,请自己从PageHelper返回的数据中得到。参看下面注释内容: //Page<Object> page = PageHelper.startPage(1, 5); //MemberDetails member = memberService.getMemberDetailsByToken(token); //return Resp.success(member, page.getTotal(), page.getPageNum(), page.getPageSize());}@ResponseBody@RequestMapping("/json2")public Resp json2(String token) { MemberDetails member = memberService.getMemberDetailsByToken(token); Map<String, Object> ext = new HashMap<>(); ext.put("msg", "扩展内容"); return Resp.success("成功了", member, ext, 5L, 1, 3);}@ResponseBody@RequestMapping("/json3")public Resp json3(String token) { MemberDetails member = memberService.getMemberDetailsByToken(token); return Resp.fail();}@ResponseBody@RequestMapping("/json4")public Resp json4(String token) { MemberDetails member = memberService.getMemberDetailsByToken(token); Map<String, Object> ext = new HashMap<>(); ext.put("msg", "扩展内容"); return Resp.result("201", "成功了", member);}@ResponseBody@RequestMapping("/json5")public Resp json5(String token) { MemberDetails member = memberService.getMemberDetailsByToken(token); return Resp.success(member);}@ResponseBody@RequestMapping("/json6")public Resp json6(String token) { MemberDetails member = memberService.getMemberDetailsByToken(token); return Resp.httpStatus(HttpStatus.ACCEPTED, "成功了");} 7. 运行结果: 访问:http://127.0.0.1:1230/json?token=123 访问:http://127.0.0.1:1230/json2?token=123 访问:http://127.0.0.1:1230/json3?token=123 访问:http://127.0.0.1:1230/json4?token=123 访问:http://127.0.0.1:1230/json5?token=123 访问:http://127.0.0.1:1230/json6?token=123 更多用法,请查看Resp类里面的静态方法 后记: 如果大家有更好的改进建议或代码,欢迎在评论留言反馈,谢谢!","categories":[{"name":"Restful","slug":"Restful","permalink":"http://www.seifon.cn/categories/Restful/"}],"tags":[{"name":"Rest","slug":"Rest","permalink":"http://www.seifon.cn/tags/Rest/"},{"name":"Json","slug":"Json","permalink":"http://www.seifon.cn/tags/Json/"},{"name":"Util","slug":"Util","permalink":"http://www.seifon.cn/tags/Util/"}],"keywords":[{"name":"Restful","slug":"Restful","permalink":"http://www.seifon.cn/categories/Restful/"}]},{"title":"利用Filter和Spring拦截器,将用户信息动态传入Request方法","slug":"利用Filter和拦截器,将用户信息动态传入Request方法","date":"2018-04-21T12:14:00.000Z","updated":"2019-06-16T16:01:02.197Z","comments":true,"path":"2018/04/21/利用Filter和拦截器,将用户信息动态传入Request方法/","link":"","permalink":"http://www.seifon.cn/2018/04/21/利用Filter和拦截器,将用户信息动态传入Request方法/","excerpt":"","text":"前言: 在开发当中,经常会验证用户登录状态和获取用户信息。如果每次都手动调用用户信息查询接口,会非常的繁琐,而且代码冗余。为了提高开发效率,因此就有了今天这篇文章。 思路: 用户请求我们的方法会携带一个Token,通过Filter过滤器将会员信息查出来并放到request请求参数中。接着在Cotroller层的请求方法中接收一个MemberDeatails类型的参数,就能直接获得会员信息了。 详细步骤:1. Gradle引入需要的Jar包:1compile "com.fasterxml.jackson.core:jackson-databind:2.8.10" 2. 定义一个Login注解12345678@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Login { String value() default "";} 3. 定义一个MemberDetails.class,用于封装用户信息12345678910111213public class MemberDetails { private String memberId; private String memberName; private String memberNickname; private String memberPhone; private String memberEmail;} 5. 定义一个会员接口类12345678910/** * @Author: XiongFeng * @Description: 会员接口 * @Date: Created in 19:40 2018/4/10 */public interface MemberService { /** 根据TokenId获取用户信息 */ MemberDto getMemberByToken(String token);} 6. 定义一个会员接口实现类,在里面写上用户信息获取方法12345678910111213141516@Servicepublic class MemberServiceImpl implements MemberService { @Override public MemberDetails getMemberDetailsByToken(String token) { if (StringUtils.isBlank(token)) return null; if (!"123".equals(token)) return null; MemberDetails memberDetails = new MemberDetails(); memberDetails.setMemberId("123"); memberDetails.setMemberName("哈哈123"); memberDetails.setMemberEmail("[email protected]"); memberDetails.setMemberNickname("Seifon"); memberDetails.setMemberPhone("13100001111"); return memberDetails; }} 7. 定义一个Request请求包装类 通过继承HttpServletRequestWrapper类,重写它里面的多个方法,对前端传过来的参数进行重新封装。因为在Filter,虽然可以通过request.getParameterMap()拿到一个含有参数的map,但是不能直接对request里面东西进行修改操作,一旦重新修改,就会报错。后来我发现j2ee已经给我们提供了解决的办法,使用HttpServletRequestWrapper类来解决向request添加额外参数的功能。于是我对HttpServletRequest进行重新包装,在里面重新定义一个map,将以前的参数put进去,并将我们需要添加的参数放进去,达到我们想要的效果。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletRequestWrapper;import java.util.Enumeration;import java.util.HashMap;import java.util.Map;import java.util.Vector;/** * @Author: XiongFeng * @Description: 对Request请求重新包装 * @Date: Created in 11:17 2018/4/13 */public class ParameterRequestWrapper extends HttpServletRequestWrapper { private Map<String , String[]> params = new HashMap<String, String[]>(); @SuppressWarnings("unchecked") public ParameterRequestWrapper(HttpServletRequest request) { // 将request交给父类,以便于调用对应方法的时候,将其输出,其实父亲类的实现方式和第一种new的方式类似 super(request); //将参数表,赋予给当前的Map以便于持有request中的参数 this.params.putAll(request.getParameterMap()); } //重载一个构造方法 public ParameterRequestWrapper(HttpServletRequest request , Map<String , Object> extendParams) { this(request); addAllParameters(extendParams);//这里将扩展参数写入参数表 } /** * 复写获取key的方法 */ @Override public Enumeration getParameterNames() { Vector names = new Vector(params.keySet()); return names.elements(); } /** * 复写获取值value的方法 */ @Override public String getParameter(String name) { Object v = params.get(name); if (v == null) { return null; } else if (v instanceof String[]) { String[] strArr = (String[]) v; if (strArr.length > 0) { return strArr[0]; } else { return null; } } else if (v instanceof String) { return (String) v; } else { return v.toString(); } } @Override public String[] getParameterValues(String name) { Object v = params.get(name); if (v == null) { return null; } else if (v instanceof String[]) { return (String[]) v; } else if (v instanceof String) { return new String[] { (String) v }; } else { return new String[] { v.toString() }; } } public void addAllParameters(Map<String , Object>otherParams) {//增加多个参数 for(Map.Entry<String , Object>entry : otherParams.entrySet()) { addParameter(entry.getKey() , entry.getValue()); } } public void addParameter(String name , Object value) {//增加参数 if(value != null) { if(value instanceof String[]) { params.put(name , (String[])value); }else if(value instanceof String) { params.put(name , new String[] {(String)value}); }else { params.put(name , new String[] {String.valueOf(value)}); } } } /** 简单封装,请根据需求改进 */ public void addObject(Object obj) { Class<?> clazz = obj.getClass(); Method[] methods = clazz.getMethods(); try { for (Method method : methods) { if (!method.getName().startsWith("get")) { continue; } Object invoke = method.invoke(obj); if (invoke == null || "".equals(invoke)) { continue; } String filedName = method.getName().replace("get", ""); filedName = WordUtils.uncapitalize(filedName); if (invoke instanceof Collection) { Collection collections = (Collection) invoke; if (collections != null && collections.size() > 0) { String[] strings = (String[]) collections.toArray(); addParameter(filedName, strings); return; } } addParameter(filedName, invoke); } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }} 8. 定义一个过滤器 在这个过滤里面,主要校验Token是否有效以及将会员信息添加到request。首先,从Request请求头中拿到前端传过来的Token,并使用Token调用会员信息获取接口,得到用户的资料,然后将用户信息put到ParameterMap中,这个ParameterMap是我们通过ParameterRequestWrapper重新包装的一个map,因此可以在里面添加会员的参数,然后将新的request传递出去。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.stereotype.Component;import javax.servlet.*;import javax.servlet.annotation.WebFilter;import javax.servlet.http.HttpServletRequest;import java.io.IOException;import java.util.Date;import java.util.HashMap;import java.util.Map;/** * @Author: XiongFeng * @Description: 会员登录信息过滤器 * @Date: Created in 11:17 2018/4/13 */@Component@WebFilter(urlPatterns = "/*")public class MemberFilter implements Filter { MemberService memberService = new MemberServiceImpl(); ObjectMapper objectMapper = new ObjectMapper(); @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; String tokenId = req.getHeader("X-Authorization"); if (tokenId == null || "".equals(tokenId) || tokenId.isEmpty()) { chain.doFilter(request, response); return; } MemberDetails memberDetails = memberService.getMemberDetailsByToken(tokenId); if (memberDetails == null) this.respFail(response); ParameterRequestWrapper requestWrapper = new ParameterRequestWrapper(req); requestWrapper.addObject(memberDetails); chain.doFilter(requestWrapper, response); } /** 返回失败结果Json数据 */ private void respFail(ServletResponse response) throws IOException { Map<String, Object> map = new HashMap<>(); map.put("status", 500); map.put("message", "登录失效,请登录"); map.put("data", null); String s = objectMapper.writeValueAsString(map); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); response.getWriter().write(s); } @Override public void destroy() { }} 9. 定义一个SpringMVC拦截器 在这个拦截器里面,主要验证Controller方法中是否需要MemberDetails和是否标了@Login注解。首先,从HandlerMethod中获取所有入参,看有没有需要MemberDetails参数,如果有,就从HttpServletRequest中拿memberId,如果不存在说明没有登录,存在就通过。然后HandlerMethod获取@Login注解,判断是否存在,如果存在,就看有没有memberId,没有就不通过。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768package cn.seifon.paymodle.interceptor;import cn.seifon.paymodle.annotations.Login;import cn.seifon.paymodle.dto.MemberDetails;import cn.seifon.paymodle.service.manager.member.MemberService;import cn.seifon.paymodle.service.manager.member.impl.MemberServiceImpl;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.core.MethodParameter;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.lang.reflect.Type;import java.util.HashMap;import java.util.Map;/** * @Author: XiongFeng * @Description: 会员登录信息拦截器 * @Date: Created in 11:17 2018/4/13 */public class MemberInterceptor extends HandlerInterceptorAdapter { ObjectMapper objectMapper = new ObjectMapper(); MemberService memberService = new MemberServiceImpl(); public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { HandlerMethod method = (HandlerMethod) handler; String[] memberIds = request.getParameterValues("memberId"); MethodParameter[] methodParameters = method.getMethodParameters(); //判断方法类是否有MemberDetails入参 if (methodParameters.length > 0) { for (MethodParameter methodParameter : methodParameters) { Type genericParameterType = methodParameter.getGenericParameterType(); String typeName = genericParameterType.getTypeName(); if (!typeName.equals(MemberDetails.class.getTypeName())) continue; if (memberIds == null || memberIds.length <= 0) return this.respFail(response); //如果找不到用户信息就返回失败 break; } } //判断是否有Login注解 Login login = method.getMethodAnnotation(Login.class); if (login == null) return true; if (memberIds == null || memberIds.length <= 0) return this.respFail(response); //如果找不到用户信息就返回失败 return true; } /** 返回失败结果Json数据 */ private boolean respFail(HttpServletResponse response) throws IOException { Map<String, Object> map = new HashMap<>(); map.put("status", 500); map.put("message", "登录失效,请登录"); map.put("data", null); String s = objectMapper.writeValueAsString(map); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); response.getWriter().write(s); return false; }} 10. 将拦截器注册到WebMvcConfigurer中123456789101112@Configurationpublic class MyWebAppConfigurer extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry) { // addPathPatterns 用于添加拦截规则 registry.addInterceptor(new MemberInterceptor()).addPathPatterns("/**"); super.addInterceptors(registry); }} 11. 定义会员Controller 经过一个过滤器和一个拦截器,request请求终于来到了我们Controller层。这时候,我们只需要在方法里面写入MemberDetails memberDetails 就OK了,不用做任何操作,我们就可以获取会员信息了,是不是炒鸡方便!另外还可以在方法上标@Login注解。 12345678910111213141516@RestControllerpublic class MemberController { @RequestMapping("/token") @Login public Map<String, Object> getUser(MemberDetails memberDetails) { //User user = userManager.selectByPrimaryKey(id); Map<String, Object> map = new LinkedHashMap<>(); map.put("status", 200); map.put("message", "请求成功"); map.put("data", memberDetails); return map; }} 运行结果: 遇到的坑: 当时我尝试过把会员参数放到session域中的Attribute,也尝试过在Model里setAttribute。后来发现这是行不通的,在filter中直接使用request.setAttribute()是无效的。放在Modle也是可行,但是Controller里面的方法需要加@ModelAttribute(“…”)才能得到用户信息,很不方便。唯有通过request.getParameterMap() put()进去,才是最方便的。 一开始我没想到用过滤器,因此我就尝试在拦截器里,直接通过ParameterRequestWrapper对request包装,后来发现不管我怎么弄都不成功。当时非常绝望,后来想了想会不会是拦截器不支持重新包装request,于是我就通过filter去做,没想到成功了。这时,我想既然用到了filter,那干脆直接在filter里面获取@Login注解和获取方法参数得了,后来发现filter里面拿不到方法的信息,哭。后来想到一个办法,可以通过先filter,后拦截器。于是就成功了! 后记: 这篇文章只是记录了我的一点小小经验,如果有什么不对的地方或者有更好的方法,请大家在评论里留言指正! 参考文章:http://www.importnew.com/19023.html","categories":[{"name":"Spring","slug":"Spring","permalink":"http://www.seifon.cn/categories/Spring/"}],"tags":[{"name":"拦截器","slug":"拦截器","permalink":"http://www.seifon.cn/tags/拦截器/"},{"name":"Filter","slug":"Filter","permalink":"http://www.seifon.cn/tags/Filter/"}],"keywords":[{"name":"Spring","slug":"Spring","permalink":"http://www.seifon.cn/categories/Spring/"}]},{"title":"Nginx的五种负载算法模式","slug":"Nginx的五种负载算法模式","date":"2017-09-04T17:03:00.000Z","updated":"2018-01-24T15:13:45.000Z","comments":true,"path":"2017/09/05/Nginx的五种负载算法模式/","link":"","permalink":"http://www.seifon.cn/2017/09/05/Nginx的五种负载算法模式/","excerpt":"","text":"1、轮询(默认)每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。 例如: 1234upstream bakend { server 192.168.0.14; server 192.168.0.15; } 2、weight权重指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。 例如: 1234upstream bakend { server 192.168.0.14 weight=10; server 192.168.0.15 weight=10; } 3、ip_hash每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。 例如: 12345upstream bakend { ip_hash; server 192.168.0.14:88; server 192.168.0.15:80; } 4、fair(第三方)按后端服务器的响应时间来分配请求,响应时间短的优先分配。 12345upstream backend { server server1; server server2; fair; } 5、url_hash(第三方)按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。 例:在upstream中加入hash语句,server语句中不能写入weight等其他的参数,hash_method是使用的hash算法 123456upstream backend { server squid1:3128; server squid2:3128; hash $request_uri; hash_method crc32; } tips: 1234567upstream bakend{#定义负载均衡设备的Ip及设备状态 ip_hash; server 127.0.0.1:9090 down; server 127.0.0.1:8080 weight=2; server 127.0.0.1:6060; server 127.0.0.1:7070 backup; } 在需要使用负载均衡的server中增加 1proxy_pass http://bakend/; 每个设备的状态设置为: 1.down 表示单前的server暂时不参与负载 2.weight 默认为1.weight越大,负载的权重就越大。 3.max_fails :允许请求失败的次数默认为1.当超过最大次数时,返回proxy_next_upstream 模块定义的错误 4.fail_timeout:max_fails次失败后,暂停的时间。 5.backup: 其它所有的非backup机器down或者忙的时候,请求backup机器。所以这台机器压力会最轻。","categories":[{"name":"Nginx","slug":"Nginx","permalink":"http://www.seifon.cn/categories/Nginx/"}],"tags":[{"name":"Nginx","slug":"Nginx","permalink":"http://www.seifon.cn/tags/Nginx/"},{"name":"负载均衡","slug":"负载均衡","permalink":"http://www.seifon.cn/tags/负载均衡/"}],"keywords":[{"name":"Nginx","slug":"Nginx","permalink":"http://www.seifon.cn/categories/Nginx/"}]},{"title":"高CPU占用调优","slug":"高CPU占用调优","date":"2017-09-04T16:39:00.000Z","updated":"2019-06-16T16:01:02.203Z","comments":true,"path":"2017/09/05/高CPU占用调优/","link":"","permalink":"http://www.seifon.cn/2017/09/05/高CPU占用调优/","excerpt":"","text":"根据top命令,发现PID为28555的Java进程占用CPU高达200%,出现故障。 通过查看进程1ps aux | grep PID 可以进一步确定是tomcat进程出现了问题。 但是,怎么定位到具体线程或者代码呢? 首先显示线程列表:1ps -mp pid -o THREAD,tid,time 找到了耗时最高的线程28802,占用CPU时间快两个小时了! 其次将需要的线程ID转换为16进制格式:1printf "%x\\n" tid 最后打印线程的堆栈信息:1jstack pid |grep tid(16进制线程ID) –A60 找到有问题的java代码(省略),进行业务调整和编码修改 最后,总结下排查CPU故障的方法和技巧有哪些: 1、top命令:Linux命令。可以查看实时的CPU使用情况。也可以查看最近一段时间的CPU使用情况。 2、PS命令:Linux命令。强大的进程状态监控命令。可以查看进程以及进程中线程的当前CPU使用情况。属于当前状态的采样数据。 3、jstack:Java提供的命令。可以查看某个进程的当前线程栈运行情况。根据这个命令的输出可以定位某个进程的所有线程的当前运行状态、运行代码,以及是否死锁等等。","categories":[{"name":"JAVA调优","slug":"JAVA调优","permalink":"http://www.seifon.cn/categories/JAVA调优/"}],"tags":[{"name":"调优","slug":"调优","permalink":"http://www.seifon.cn/tags/调优/"},{"name":"Jstack","slug":"Jstack","permalink":"http://www.seifon.cn/tags/Jstack/"},{"name":"栈","slug":"栈","permalink":"http://www.seifon.cn/tags/栈/"}],"keywords":[{"name":"JAVA调优","slug":"JAVA调优","permalink":"http://www.seifon.cn/categories/JAVA调优/"}]},{"title":"开关指定端口,以80为例","slug":"开关指定端口,以80为例","date":"2017-07-09T06:02:54.000Z","updated":"2018-01-24T15:13:45.000Z","comments":true,"path":"2017/07/09/开关指定端口,以80为例/","link":"","permalink":"http://www.seifon.cn/2017/07/09/开关指定端口,以80为例/","excerpt":"开启80端口:1iptables -I INPUT -p tcp --dport 80 -j ACCEPT","text":"开启80端口:1iptables -I INPUT -p tcp --dport 80 -j ACCEPT 关闭80端口:1iptables -I INPUT -p tcp --dport 80 -j DROP 保存并重启Iptables:12/etc/init.d/iptables saveservice iptables restart","categories":[{"name":"Iptables","slug":"Iptables","permalink":"http://www.seifon.cn/categories/Iptables/"}],"tags":[{"name":"centos","slug":"centos","permalink":"http://www.seifon.cn/tags/centos/"},{"name":"linux","slug":"linux","permalink":"http://www.seifon.cn/tags/linux/"},{"name":"iptables","slug":"iptables","permalink":"http://www.seifon.cn/tags/iptables/"},{"name":"防火墙","slug":"防火墙","permalink":"http://www.seifon.cn/tags/防火墙/"}],"keywords":[{"name":"Iptables","slug":"Iptables","permalink":"http://www.seifon.cn/categories/Iptables/"}]},{"title":"Jenkins环境搭建","slug":"Jenkins环境搭建","date":"2017-07-07T17:52:00.000Z","updated":"2019-06-16T16:01:02.185Z","comments":true,"path":"2017/07/08/Jenkins环境搭建/","link":"","permalink":"http://www.seifon.cn/2017/07/08/Jenkins环境搭建/","excerpt":"","text":"上传以下的包到/tmp/目录下:1234567891011jdk-8u121-linux-x64.tar.gzapache-tomcat-7.0.75.tar.gzapache-maven-3.5.0-bin.tar.gzMySQL-client-5.5.52-1.el6.x86_64.rpmMySQL-devel-5.5.52-1.el6.x86_64.rpmMySQL-server-5.5.52-1.el6.x86_64.rpmMySQL-shared-5.5.52-1.el6.x86_64.rpmgit-2.9.3.tar.gzjenkins.war 解压包到/opt/目录下:12345tar -xvzf /tmp/jdk-8u121-linux-x64.tar.gz -C /opt/tar -xvzf /tmp/apache-tomcat-7.0.75.tar.gz -C /opt/tar -xvzf /tmp/apache-maven-3.5.0-bin.tar.gz -C /opt/ 配置环境变量:12345678910111213vim /etc/profileJAVA_HOME=/opt/jdk1.8.0_121CATALINA_HOME=/opt/apache-tomcat-7.0.75MAVEN_HOME=/opt/apache-maven-3.5.0PATH=/opt/jdk1.8.0_121/bin:/opt/apache-tomcat-7.0.75/bin:/opt/apache-maven-3.5.0/bin:$PATHCLASSPATH=.:/opt/jdk1.8.0_121/lib/dt.jar:/opt/jdk1.8.0_121/lib/tools.jarexport JAVA_HOME PATH CLASSPATH CATALINA_HOME MAVEN_HOMEsource /etc/profile 配置Tomcat自启动: (1)、在/etc/init.d目录下创建文件:tomcat 内容如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768#!/bin/bash# This is the init script for starting up the# Jakarta Tomcat server## chkconfig: 345 91 10# description: Starts and stops the Tomcat daemon.## Source function library.. /etc/rc.d/init.d/functions# Get config.. /etc/sysconfig/network# Check that networking is up.[ "${NETWORKING}" = "no" ] && exit 0export JAVA_HOME=/opt/jdk1.8.0_121export CATALINA_HOME=/opt/apache-tomcat-7.0.75startup=$CATALINA_HOME/bin/startup.shshutdown=$CATALINA_HOME/bin/shutdown.shstart(){ echo -n "Starting Tomcat service:" cd $tomcat_home $startup echo "tomcat is succeessfully started up"}stop(){ echo -n "Shutting down tomcat: " cd $tomcat_home $shutdown echo "tomcat is succeessfully shut down."}status(){ numproc=`ps -ef | grep catalina | grep -v "grep catalina" | wc -l` if [ $numproc -gt 0 ]; then echo "Tomcat is running..." else echo "Tomcat is stopped..." fi}restart(){ stop start}# See how we were called.case "$1" instart) start ;;stop) stop ;;status) status ;;restart) restart ;;*) echo $"Usage: $0 {start|stop|status|restart}" exit 1esac (2)、配置权限,并加入自启: 1234cd /etc/init.dchmod 755 ./tomcatchkconfig --add tomcatchkconfig tomcat on (3)、设置URIEncoding=”UTF-8” 1vim /opt/apache-tomcat-7.0.75/conf/server.xml (4)、给Tomcat设置用户名密码 1234567vim /opt/apache-tomcat-7.0.75/conf/tomcat-users.xml<role rolename="manager-gui"/><role rolename="manager-script"/><role rolename="manager-jmx"/><role rolename="manager-status"/><user username="tomcat_user" password="123456" roles="manager-gui,manager-script,manager-jmx,manager-status" /> (5)、打开/etc/sysconfig/iptables 加入以下代码: 1-A INPUT -m state --state NEW -m tcp -p tcp --dport 8080 -j ACCEPT (6)、重启防火墙服务 1service iptables restart 安装Mysql服务: 安装服务器端 1234rpm -ivh /tmp/MySQL-server-5.5.52-1.el6.x86_64.rpm如果出现报错就执行:rpm -e --nodeps mysql-libs-5.1.73-7.el6.x86_64 安装客户端 1rpm -ivh /tmp/MySQL-client-5.5.52-1.el6.x86_64.rpm 启动MySQL服务 1service mysql start 修改MySQL服务器登录密码 1/usr/bin/mysqladmin -u root password 'root' 修改防火墙端口: 12345vim /etc/sysconfig/iptables-A INPUT -m state --state NEW -m tcp -p tcp --dport 3306 -j ACCEPTservice iptables restart 修改MySQL服务器访问权限 第一步:登录MySQL服务器 第二步:use mysql; 第三步:查看user表部分内容 123456789101112mysql> select host,user,password from user; +--------------+------+-------------------------------------------+ | host | user | password | +--------------+------+-------------------------------------------+ | localhost | root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B | | computer0325 | root | | | 127.0.0.1 | root | | | ::1 | root | | | localhost | | | | computer0325 | | | +--------------+------+-------------------------------------------+ 6 rows in set (0.00 sec) 第四步:插入特定数据 1insert into user(host,user,password) values('%','root','*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B'); 第五步:再次查看user表内容 123456789101112mysql> select host,user,password from user; +--------------+------+-------------------------------------------+ | host | user | password | +--------------+------+-------------------------------------------+ | localhost | root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B | | computer0325 | root | | | 127.0.0.1 | root | | | ::1 | root | | | localhost | | | | computer0325 | | | | % | root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B | +--------------+------+-------------------------------------------+ 第六步:为root@%授权 1mysql> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'root' WITH GRANT OPTION; 第七步:退出MySQL 1mysql> exit; 第八步:重启MySQL服务 1service mysql restart; 编译安装git 安装依赖: 123yum install curl-devel expat-devel gettext-devel openssl-devel zlib-develyum install gcc perl-ExtUtils-MakeMaker 删除已有的git: 1yum remove git 编译git: 123456tar -xvzf ./git-2.9.3.tar.gz -C /opt/cd /opt/git-2.9.3/make prefix=/usr/local/git allmake prefix=/usr/local/git installecho "export PATH=$PATH:/usr/local/git/bin" >> /etc/bashrcsource /etc/bashrc 检查版本: 1git --version 部署Jenkins.war包,以及初始化环境:1cp /tmp/jenkins.war /opt/apache-tomcat-7.0.75/webapps/ 通过浏览器访问Tomcat下的jenkins应用 1例如:http://[server-address]:8080/jenkins/ 输入内置密钥串 先不安装插件,后面有需要时再安装,点右上角的× 修改默认管理员密码 安装GitHub plugin 和 Deploy to container Plugin 插件 点击系统管理,进入系统设置界面 Configure Global Security 123①勾选允许用户注册,这样万一我们忘记了之前的账号密码还可以再注册一个②勾选任何用户可以做任何事(没有任何限制)③取消防止跨站点请求伪造勾选 Global Tool Configuration ①设置Maven ②设置JDK,不要选自动安装,因为我们自己安装过了 ③设置MAVEN_HOME,同样不要自动安装 ④设置Git:/usr/local/git/bin/git","categories":[{"name":"Jenkins","slug":"Jenkins","permalink":"http://www.seifon.cn/categories/Jenkins/"}],"tags":[{"name":"Jenkins","slug":"Jenkins","permalink":"http://www.seifon.cn/tags/Jenkins/"},{"name":"CI","slug":"CI","permalink":"http://www.seifon.cn/tags/CI/"}],"keywords":[{"name":"Jenkins","slug":"Jenkins","permalink":"http://www.seifon.cn/categories/Jenkins/"}]},{"title":"Yaml语法说明","slug":"Yaml语法说明","date":"2017-06-26T17:19:00.000Z","updated":"2018-01-24T15:13:45.000Z","comments":true,"path":"2017/06/27/Yaml语法说明/","link":"","permalink":"http://www.seifon.cn/2017/06/27/Yaml语法说明/","excerpt":"","text":"简介YAML 是专门用来写配置文件的语言,非常简洁和强大,远比 JSON 格式方便。 语法规则 大小写敏感 使用缩进表示层级关系 缩进时不允许使用Tab键,只允许使用空格。 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可 :冒号后面必须有空格 #表示注释,从这个字符一直到行尾,都会被解析器忽略。 YAML支持三种数据结构: 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary) 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list) 纯量(scalars):单个的、不可再分的值 对象 一、对象的一组键值对,使用冒号结构表示。 1animal: pets 转为 JavaScript 如下: 1{ animal: 'pets' } 二、将所有键值对写成一个行内对象。 1hash: { name: Steve, foo: bar } 转为 JavaScript 如下 1{ hash: { name: 'Steve', foo: 'bar' } } 数组 一、一组连词线开头的行,构成一个数组。 123- Cat- Dog- Goldfish 转为 JavaScript 如下。 1[ 'Cat', 'Dog', 'Goldfish' ] 数据结构的子成员是一个数组,则可以在该项下面缩进一个空格。 1234- - Cat - Dog - Goldfish 转为 JavaScript 如下。 1[ [ 'Cat', 'Dog', 'Goldfish' ] ] 二、数组也可以采用行内表示法。 1animal: [Cat, Dog] 转为 JavaScript 如下。 1{ animal: [ 'Cat', 'Dog' ] } 复合结构 对象和数组可以结合使用,形成复合结构。 123456789languages: - Ruby - Perl - Python websites: YAML: yaml.org Ruby: ruby-lang.org Python: python.org Perl: use.perl.org 转为 JavaScript 如下: 123456{ languages: [ 'Ruby', 'Perl', 'Python' ], websites: { YAML: 'yaml.org', Ruby: 'ruby-lang.org', Python: 'python.org', Perl: 'use.perl.org' } } 纯量 纯量是最基本的、不可再分的值。以下数据类型都属于 JavaScript 的纯量。 字符串布尔值整数浮点数Null时间日期 数值直接以字面量的形式表示。 1number: 12.30 转为 JavaScript 如下。 1{ number: 12.30 } 布尔值用true和false表示。 1isSet: true 转为 JavaScript 如下。 1{ isSet: true } null用~表示。 1parent: ~ 转为 JavaScript 如下。 1{ parent: null } 时间采用 ISO8601 格式。 1iso8601: 2001-12-14t21:59:43.10-05:00 转为 JavaScript 如下。 1{ iso8601: new Date('2001-12-14t21:59:43.10-05:00') } 日期采用复合 iso8601 格式的年、月、日表示。 1date: 1976-07-31 转为 JavaScript 如下。 1{ date: new Date('1976-07-31') } YAML 允许使用两个感叹号,强制转换数据类型。 12e: !!str 123f: !!str true 转为 JavaScript 如下。 1{ e: '123', f: 'true' } 字符串 字符串是最常见,也是最复杂的一种数据类型。字符串默认不使用引号表示。 1str: 这是一行字符串 转为 JavaScript 如下。 1{ str: '这是一行字符串' } 如果字符串之中包含空格或特殊字符,需要放在引号之中。 1str: '内容: 字符串' 转为 JavaScript 如下。 1str: '内容: 字符串' 单引号和双引号都可以使用,双引号不会对特殊字符转义。 12s1: '内容\\n字符串's2: "内容\\n字符串" 转为 JavaScript 如下。 1{ s1: '内容\\\\n字符串', s2: '内容\\n字符串' } 单引号之中如果还有单引号,必须连续使用两个单引号转义。 1str: 'labor''s day' 转为 JavaScript 如下。 1{ str: 'labor\\'s day' } 字符串可以写成多行,从第二行开始,必须有一个单空格缩进。换行符会被转为空格。 123str: 这是一段 多行 字符串 转为 JavaScript 如下。 1{ str: '这是一段 多行 字符串' } 多行字符串可以使用|保留换行符,也可以使用>折叠换行。 123456this: | Foo Barthat: > Foo Bar 转为 JavaScript 代码如下。 1{ this: 'Foo\\nBar\\n', that: 'Foo Bar\\n' } +表示保留文字块末尾的换行,-表示删除字符串末尾的换行。 123456789s1: | Foos2: |+ Foos3: |- Foo 转为 JavaScript 代码如下。 1{ s1: 'Foo\\n', s2: 'Foo\\n\\n\\n', s3: 'Foo' } 字符串之中可以插入 HTML 标记。 1234message: | 段落 转为 JavaScript 如下。 123{ message: '\\n\\n 段落\\n\\n' } 引用 锚点&和别名*,可以用来引用。 1234567891011defaults: &defaults adapter: postgres host: localhostdevelopment: database: myapp_development <<: *defaultstest: database: myapp_test <<: *defaults 等同于下面的代码。 12345678910111213defaults: adapter: postgres host: localhostdevelopment: database: myapp_development adapter: postgres host: localhosttest: database: myapp_test adapter: postgres host: localhost &用来建立锚点(defaults),<<表示合并到当前数据,*用来引用锚点。 下面是另一个例子。 12345- &showell Steve - Clark - Brian - Oren - *showell 转为 JavaScript 代码如下。 1[ 'Steve', 'Clark', 'Brian', 'Oren', 'Steve' ] 函数和正则表达式的转换 这是 JS-YAML 库特有的功能,可以把函数和正则表达式转为字符串。 123# example.ymlfn: function () { return 1 }reg: /test/ 解析上面的 yml 文件的代码如下。 1234567891011var yaml = require('js-yaml');var fs = require('fs');try { var doc = yaml.load( fs.readFileSync('./example.yml', 'utf8') ); console.log(doc);} catch (e) { console.log(e);} 从 JavaScript 对象还原到 yaml 文件的代码如下。 1234567891011121314151617var yaml = require('js-yaml');var fs = require('fs');var obj = { fn: function () { return 1 }, reg: /test/};try { fs.writeFileSync( './example.yml', yaml.dump(obj), 'utf8' );} catch (e) { console.log(e);}","categories":[{"name":"Yaml","slug":"Yaml","permalink":"http://www.seifon.cn/categories/Yaml/"}],"tags":[{"name":"Yaml","slug":"Yaml","permalink":"http://www.seifon.cn/tags/Yaml/"},{"name":"yml","slug":"yml","permalink":"http://www.seifon.cn/tags/yml/"}],"keywords":[{"name":"Yaml","slug":"Yaml","permalink":"http://www.seifon.cn/categories/Yaml/"}]},{"title":"Mysql异地定时备份方法","slug":"Mysql异地定时备份方法","date":"2017-05-02T16:51:00.000Z","updated":"2018-01-24T15:13:45.000Z","comments":true,"path":"2017/05/03/Mysql异地定时备份方法/","link":"","permalink":"http://www.seifon.cn/2017/05/03/Mysql异地定时备份方法/","excerpt":"","text":"两台linux服务器: 一台199.20.30.222(mysql服务器) 一台199.20.30.226(与mysql互通的一台server) 思路:mysql本地做一份备份;226Server做一份同步备份,脚本放在222上面,定时任务也设置在222上面。 (1)mysql本地备份:1234#!/bin/sh#mysql_backupDATE=`date +%Y%m%d`/opt/mysql/bin/mysqldump --opt jsweb -u root > /opt/mysqlbackup/$DATE.sql (2)将本地备份同步到远端226上面:12345#!/bin/bash#date:2013/09/24#function:The script is used copying mysqlbackup to 226DATE=`date +%Y%m%d`scp /opt/mysqlbackup/$DATE.sql [email protected]:/data/2013backup/mysql/ (3)222上面设置定时任务:123vim/etc/crontable OR crontable -e30 1 * * * /bin/sh /home/jiaoben/backup.sh ===============每天1:30执行30 3 * * * /bin/sh /home/jiaoben/scp.sh ===============每天3:30执行 注意:两个linux之间相互拷贝文件,使用scp工具必须输入远端密码,所以在进行(2)时,需要规避输入密码,否则自动备份到remote不会生效, 本人采用的方法:使两个服务器互相信任,执行scp时,就可以免输入密码。 12345ssh-keygen -t rsacd /root/.ssh/scp id_rsa.pub [email protected]:/root/ssh/ssh [email protected] /root/.ssh/id_isa_pub /root/.ssh/authorized_key 时间过长备份的mysql就需要定时清理12find /opt/mysqlbackup/ -type f -name \\*.sql -mtime +10 -exec rm -fr {} \\;find /opt/mysqlbackup/ -type f -name "*.sql" -mtime +10 -exec rm -fr {} \\;","categories":[{"name":"Mysql","slug":"Mysql","permalink":"http://www.seifon.cn/categories/Mysql/"}],"tags":[{"name":"Mysql","slug":"Mysql","permalink":"http://www.seifon.cn/tags/Mysql/"},{"name":"数据库","slug":"数据库","permalink":"http://www.seifon.cn/tags/数据库/"},{"name":"备份","slug":"备份","permalink":"http://www.seifon.cn/tags/备份/"},{"name":"定时","slug":"定时","permalink":"http://www.seifon.cn/tags/定时/"},{"name":"容灾","slug":"容灾","permalink":"http://www.seifon.cn/tags/容灾/"}],"keywords":[{"name":"Mysql","slug":"Mysql","permalink":"http://www.seifon.cn/categories/Mysql/"}]},{"title":"CentOS7防火墙Firewalld配置和使用","slug":"CentOS7防火墙Firewalld配置和使用","date":"2017-05-02T06:55:00.000Z","updated":"2018-01-24T15:13:45.000Z","comments":true,"path":"2017/05/02/CentOS7防火墙Firewalld配置和使用/","link":"","permalink":"http://www.seifon.cn/2017/05/02/CentOS7防火墙Firewalld配置和使用/","excerpt":"添加放行端口:1firewall-cmd --permanent --zone=public --add-port=80/tcp","text":"添加放行端口:1firewall-cmd --permanent --zone=public --add-port=80/tcp 删除放行端口:1firewall-cmd --permanent --zone=public --remove-port=80/tcp 热加载:1firewall-cmd --reload 命令含义:12345--zone #作用域--add-port=80/tcp #添加端口,格式为:端口/通讯协议--permanent #永久生效,没有此参数重启后失效 查看防火墙配置:1cat /etc/firewalld/zones/public.xml | less 查看防火墙状态1systemctl status firewalld.service 启动防火墙1systemctl start firewalld.service 关闭防火墙1systemctl stop firewalld.service 重新启动防火墙1systemctl restart firewalld.service","categories":[{"name":"Iptables","slug":"Iptables","permalink":"http://www.seifon.cn/categories/Iptables/"}],"tags":[{"name":"centos","slug":"centos","permalink":"http://www.seifon.cn/tags/centos/"},{"name":"linux","slug":"linux","permalink":"http://www.seifon.cn/tags/linux/"},{"name":"iptables","slug":"iptables","permalink":"http://www.seifon.cn/tags/iptables/"},{"name":"防火墙","slug":"防火墙","permalink":"http://www.seifon.cn/tags/防火墙/"}],"keywords":[{"name":"Iptables","slug":"Iptables","permalink":"http://www.seifon.cn/categories/Iptables/"}]},{"title":"出圈算法","slug":"出圈算法","date":"2017-04-12T16:58:00.000Z","updated":"2018-01-24T15:13:45.000Z","comments":true,"path":"2017/04/13/出圈算法/","link":"","permalink":"http://www.seifon.cn/2017/04/13/出圈算法/","excerpt":"","text":"题目:有 people 个人站成一个圈,第一个人开始数数(从1开始),每数到 num 或者 num 的倍数此人就退出。最后剩下的人是多少号?网上看了看,应该有很多种实现方法,在此摘录下来。 方式一:1234567static int cycle(int people, int num) { int i, r = 0; for (i = 2; i <= people; i++) { r = (r + num) % i; } return r + 1; } 方式二:12345static int cycle(int people, int num) { int i, r = 0; for (i = 2; i <= people; i++) r = (r + num) % i; return r + 1; } 方式三:1234567891011121314private static int cycle(int people, int num) { List<Integer> dataList = new LinkedList<Integer>(); for (int i = 0; i < people; i++) { dataList.add(new Integer(i + 1)); } int index = -1; while (dataList.size() > 1) { index = (index + num) % dataList.size(); dataList.remove(index); index--; } return dataList.get(0).intValue(); }","categories":[{"name":"算法","slug":"算法","permalink":"http://www.seifon.cn/categories/算法/"}],"tags":[{"name":"算法","slug":"算法","permalink":"http://www.seifon.cn/tags/算法/"},{"name":"数据结构","slug":"数据结构","permalink":"http://www.seifon.cn/tags/数据结构/"},{"name":"面试","slug":"面试","permalink":"http://www.seifon.cn/tags/面试/"}],"keywords":[{"name":"算法","slug":"算法","permalink":"http://www.seifon.cn/categories/算法/"}]}]}