[TOC] # common-spring-boot-starter 回顾ocp代码, [02.api-commons模块](02.api-commons%E6%A8%A1%E5%9D%97.md),代码耦合了dabsource处理代码,耦合了log处理代码 ![](https://img.kancloud.cn/2a/ec/2aecc2c5b9075c05d02a9adde8e5b069_1248x215.png) 在之前的章节总,我们已经将此部分拆解,做到功能职责单一,避免过度依赖,现在我们将api-commons重构,重构后的项目名称为common-spring-boot-starter ## 功能 * 公用model类 * 菜单树 * 通用工具类 * 通用异常类 * 通用拦截器 * 通用过滤器 * 通用线程池配置 ![](https://img.kancloud.cn/7d/01/7d01d0d3d93461f4856029c07f617538_1999x768.png) ``` package com.open.capacity.common.test; import java.util.List; import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; import com.open.capacity.common.model.SysMenu; public class Test { public static void main(String[] args) throws JsonProcessingException { SysMenu root = new SysMenu(); root.setId(-1L); root.setParentId(0L); SysMenu child1 = new SysMenu(); child1.setId(1L); child1.setParentId(-1L); SysMenu child11 = new SysMenu(); child11.setId(11L); child11.setParentId(1L); SysMenu child111 = new SysMenu(); child111.setId(111L); child111.setParentId(11L); SysMenu child12 = new SysMenu(); child12.setId(12L); child12.setParentId(1L); SysMenu child2 = new SysMenu(); child2.setId(2L); child2.setParentId(-1L); SysMenu child21 = new SysMenu(); child21.setId(21L); child21.setParentId(2L); SysMenu child22 = new SysMenu(); child22.setId(22L); child22.setParentId(2L); //完整树 List<SysMenu> list = Lists.newArrayList(); list.add(root); list.add(child1); list.add(child11); list.add(child12); list.add(child111); list.add(child2); list.add(child21); list.add(child22); //递归树 List<SysMenu> menuTree = list.stream().filter(t -> t.getParentId() == -1L).map((menu) -> { menu.setSubMenus(treeChildren(menu, list)); return menu; }).collect(Collectors.toList()); System.out.println(new ObjectMapper().writeValueAsString(menuTree)); //扁平子节点列表 List<SysMenu> platChildList = Lists.newArrayList(); flatChildren(child1, list,platChildList) ; System.out.println(new ObjectMapper().writeValueAsString(platChildList)); //扁平父节点列表 List<SysMenu> platParentList = Lists.newArrayList(); flatParent(child111, list,platParentList) ; System.out.println(new ObjectMapper().writeValueAsString(platParentList)); } /** * 递归子节点树 * @param root * @param all * @return */ private static List<SysMenu> treeChildren(SysMenu root, List<SysMenu> all) { return all.stream().filter(t -> root.getId().equals(t.getParentId())).map((g) -> { g.setSubMenus(treeChildren(g, all)); return g; }).collect(Collectors.toList()); } /** * 扁平子节点列表 * @param current * @param list * @param flatTree */ private static void flatChildren(SysMenu current, List<SysMenu> list , List<SysMenu> flatTree ) { if (current != null) { List<SysMenu> tempList = list.stream() .filter(t -> t.getParentId().equals(current.getId())) .collect(Collectors.toList()); flatTree.addAll(tempList); if (CollectionUtils.isNotEmpty(tempList)) { tempList.stream().forEach(item -> flatChildren(item, list , flatTree)); } } else { return; } } /** * 扁平父节点列表 * @param current * @param list * @param flatTree */ private static void flatParent(SysMenu current, List<SysMenu> list , List<SysMenu> flatTree ) { if (current != null) { List<SysMenu> tempList = list.stream() .filter(t -> t.getId().equals(current.getParentId())) .collect(Collectors.toList()); flatTree.addAll(tempList); if (CollectionUtils.isNotEmpty(tempList)) { tempList.stream().forEach(item -> flatParent(item, list , flatTree)); } } else { return; } } } ``` ## 代码 * pom依赖 ![](https://img.kancloud.cn/88/6c/886c4a6128aa5f4e338bd21f1caf0533_1731x787.png) * 代码重构 ![](https://img.kancloud.cn/07/8d/078d9d712b59db760530fc0838d40ad2_1395x616.png) ## hutool 工具类 文档:[https://www.hutool.cn/docs/#/](https://www.hutool.cn/docs/#/) ### 类型转化 ``` int a = 1 ; String aString = Convert.toStr(a) ;//整型转String System.out.println(aString); long[] b = {1L,2L,3L,4L,5L} ; String bString = Convert.toStr(b) ;//long数组 转String数组 System.out.println(bString); String[] c = {"1","2","3","4","5"}; Integer[] cArray = Convert.toIntArray(c) ;// String数组转int数组 System.out.println(cArray); String dateString ="2018-05-22 14:09:33" ; Date date = Convert.toDate(dateString) ;//时间转化 System.out.println(date); String quanjiao ="1234567" ; String banjiao =Convert.toSBC(quanjiao) ;//半角转化 System.out.println(banjiao); System.out.println(Convert.toDBC(banjiao)) ;//全角转化 String str = "明文字符串" ; String encodeString = Convert.toHex(str,CharsetUtil.CHARSET_UTF_8) ;//加密 System.out.println(encodeString); String decodeString = Convert.hexToStr(encodeString, CharsetUtil.CHARSET_UTF_8) ; //解密 System.out.println(decodeString); String unicode = Convert.strToUnicode(str) ; System.out.println(unicode); String raw = Convert.unicodeToStr(unicode); System.out.println(raw); String result = Convert.convertCharset(str, CharsetUtil.UTF_8, CharsetUtil.ISO_8859_1) ;//字符集转化 System.out.println(result); String iso = Convert.convertCharset(result, CharsetUtil.ISO_8859_1, CharsetUtil.UTF_8) ; //字符集转化 System.out.println(iso); long date = 72 ; long day = Convert.convertTime(date, TimeUnit.HOURS, TimeUnit.DAYS);//时间转化72小时3天 System.out.println(day); double price = 6449.89; System.out.println(Convert.digitToChinese(price)) ;//金钱转化 ``` ### 时间 ``` Date date = DateUtil.date(); String format = DateUtil.format(date, "yyyy-MM-dd") ; System.out.println(format); Date beginOfDate = DateUtil.beginOfDay(date) ; Date endOfDate = DateUtil.endOfDay(date) ; System.out.println(beginOfDate); System.out.println(endOfDate); date = DateUtil.date(System.currentTimeMillis()); String now = DateUtil.now(); //yyyy-MM-dd HH:mm:ss String tody = DateUtil.today();//yyyy-MM-dd DateUtil.yesterday(); DateUtil.tomorrow(); DateUtil.lastWeek(); DateUtil.lastMonth(); int age = DateUtil.ageOfNow("1998-04-04" ) ; System.out.println(age); ``` ### excel ``` List<?> row1 = CollUtil.newArrayList("aa", "bb", "cc", "dd", DateUtil.date(), 3.22676575765); List<?> row2 = CollUtil.newArrayList("aa1", "bb1", "cc1", "dd1", DateUtil.date(), 250.7676); List<?> row3 = CollUtil.newArrayList("aa2", "bb2", "cc2", "dd2", DateUtil.date(), 0.111); List<?> row4 = CollUtil.newArrayList("aa3", "bb3", "cc3", "dd3", DateUtil.date(), 35); List<?> row5 = CollUtil.newArrayList("aa4", "bb4", "cc4", "dd4", DateUtil.date(), 28.00); List<List<?>> rows = CollUtil.newArrayList(row1, row2, row3, row4, row5); BigExcelWriter writer= ExcelUtil.getBigWriter("e:"+File.separator+"xxx.xlsx"); // 一次性写出内容,使用默认样式 writer.write(rows); // 关闭writer,释放内存 writer.close(); ExcelReader reader = ExcelUtil.getReader("e:"+File.separator+"xxx.xlsx"); List<Map<String,Object>> readAll = reader.readAll(); readAll.forEach( item -> { System.out.println(item); }); ``` ### 二维码 ``` @GetMapping("/test11") public void service(HttpServletResponse response) { try (ServletOutputStream out = response.getOutputStream()) { QrCodeUtil.generate("http://www.baidu.com", QrConfig.create().setImg("logo"), "png", out); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void main(String[] args) { QrConfig config = new QrConfig(300, 300); config.setMargin(2); QrCodeUtil.generate("http://www.baidu.com", config, FileUtil.file("d:" + File.separator + "test.png")); } ``` ## 获取用户信息工具类 ![](https://img.kancloud.cn/9b/41/9b417ca45e96f5e2263aef521de08448_1645x688.png) ## BeanValidator参数校验 ![](https://img.kancloud.cn/b8/06/b806aee9e7251ea92f7b3f3c90b15052_707x337.png) ![](https://img.kancloud.cn/d3/e7/d3e7fa5f37e8a435f0e993139745c788_1046x688.png) ## 认证授权白名单配置 ![](https://img.kancloud.cn/bf/1b/bf1b68daa879bc4a817ddee99e279ec8_1603x690.png) ## token传递 traceid传递 ![](https://img.kancloud.cn/39/ac/39ac2889c61518d9501c2e02a8b3ee85_1695x626.png) ![](https://img.kancloud.cn/7c/8d/7c8d84557b84a57e1708d5123f281aa9_1720x660.png) ## 分页基础类 ![](https://img.kancloud.cn/56/28/5628a5299c355cccfd3f40521fac69f9_1666x508.png) ## 简单使用RestTemplate ``` RestTemplate template = new RestTemplate(); String uriTemplate = "http://www.baidu.com"; URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build().toUri(); String s ="{}"; RequestEntity<String> requestEntity = RequestEntity.post(uri) .accept(MediaType.APPLICATION_JSON) .header("Content-Type", "application/json") .body(s); ResponseEntity<String> exchange = template.exchange(requestEntity, String.class); String body = exchange.getBody(); System.out.println(body); ``` ## RestTemplate连接复用 ``` package com.open.capacity.common.rest; import java.util.Arrays; import org.apache.http.HttpResponse; import org.apache.http.conn.ConnectionKeepAliveStrategy; import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HttpContext; import com.open.capacity.common.util.StringUtil; import cn.hutool.core.convert.Convert; public class CustomConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy { private final long DEFAULT_SECONDS = 30; private final String DEFAULT_HEAD = "timeout"; /** * 最大keep alive的时间(秒钟) * 这里默认为30秒,可以根据实际情况设置。可以观察客户端机器状态为TIME_WAIT的TCP连接数,如果太多,可以增大此值。 */ @Override public long getKeepAliveDuration(HttpResponse response, HttpContext context) { return Arrays.asList(response.getHeaders(HTTP.CONN_KEEP_ALIVE)).stream() .filter(h -> StringUtil.equalsIgnoreCase(h.getName(), DEFAULT_HEAD) && StringUtil.isNumeric(h.getValue())) .findFirst().map(h -> Convert.toLong(h.getValue(), DEFAULT_SECONDS)).orElse(DEFAULT_SECONDS) * 1000; } } ``` ## 优化RestTemplateConfig ![](https://img.kancloud.cn/1b/24/1b24ad2e0cca7085129671a39d92027a_967x365.png) 由于RestTemplate默认并发数100(配置默认:org.springframework.remoting.httpinvoker.HttpComponentsHttpInvokerRequestExecutor),导致服务间调用阻塞,占用大量时间时间,需要按以下脚本优化, ![](https://img.kancloud.cn/94/08/94080a3a351a573d9136546376e8f543_1687x602.png) ## 实时获取QPS ![](https://img.kancloud.cn/17/d5/17d580785f4e27b60ad022ea2f2d27c7_871x611.png) ``` @RestController @RequestMapping("/api/v1") @Slf4j public class DemoController { private FlowHelper flowHelper = new FlowHelper(FlowType.Minute); @GetMapping("/test") public Result<Flower> testApi() { try{ long startTime = TimeUtil.currentTimeMillis(); // 业务逻辑 Thread.sleep(1000); // 计算耗时 long rt = TimeUtil.currentTimeMillis() - startTime; flowHelper.incrSuccess(rt); Flower flower = flowHelper.getFlow(FlowType.Minute); System.out.println("总请求数:"+flower.total()); System.out.println("成功请求数:"+flower.totalSuccess()); System.out.println("异常请求数:"+flower.totalException()); System.out.println("平均请求耗时:"+flower.avgRt()); System.out.println("最大请求耗时:"+flower.maxRt()); System.out.println("最小请求耗时:"+flower.minRt()); System.out.println("平均请求成功数(每毫秒):"+flower.successAvg()); System.out.println("平均请求异常数(每毫秒):"+flower.exceptionAvg()); return Result.succeed("ok"); }catch (Exception e){ flowHelper.incrException(); return Result.failed("ko"); } } ``` ## 自定义异常 ![](https://img.kancloud.cn/c4/20/c4201d67cc62bd2e441f8345a2acf97f_1550x401.png) ## 统一异常处理 ![](https://img.kancloud.cn/7f/af/7faf91564b0277307f0315b8a1e3dc55_1606x474.png) [25.统一业务异常处理](27.%E7%BB%9F%E4%B8%80%E5%BC%82%E5%B8%B8.md) ## 父子线程异步传递问题 ![](https://img.kancloud.cn/97/7b/977b30c2be4c1d980243384b25eabea9_1730x744.png) ## DefaultClientDetails 应用信息基础类 ![](https://img.kancloud.cn/5a/02/5a027273873e399c33090bf25ef4f282_984x591.png) #### uaa-server-spring-boot-starter 将DefaultClientDetails信息存储到redis ![](https://img.kancloud.cn/b3/a8/b3a808a5770f2235cda0badfc6d8755e_1687x641.png) #### 网关利用DefaultClientDetails的clientid得到具体是否需要限制调用次数,以及限制次数限制调用次数 ![](https://img.kancloud.cn/05/82/058236930f69cd6b17d40932eb426845_1674x535.png) ## feign 业务异常处理 > OpenFeign 提供了这种简单的方式来使用Restful服务,这大大降低了进行接口调用的复杂程度。 对于错误的处理, 对于客户端我们实现了自己的 UserErrorDecoder 来将请求异常转换为对于的异常类,示例如下 ![](https://img.kancloud.cn/35/d3/35d3888a1f5cdc512e30fe22a9d7ef08_1629x559.png) > 需要注意的是, 当业务返回{"resp_code":"","resp_msg":""}(新版本采用({"code" :"","msg":""}))时,异常应当是 HystrixBadRequestException 的子类对象,原因在于此类异常视作业务异常,而不是由于故障导致的异常,所以不应当被Hystrix计算为失败请求,并引发断路器动作,这一点**非常重要**。 SynchronousMethodHandler 处理 ![](https://img.kancloud.cn/5f/e1/5fe19c82db76ca3ea2c8a51fcf0ad9b9_1851x696.png) ## hystrix舱壁模式下RequestContextHolder的传递问题 > java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request ![](https://img.kancloud.cn/fe/2c/fe2c1a7db3bfa0aa665bbe82ff143573_1493x543.png) ![](https://img.kancloud.cn/87/18/8718189b1f3b41fac057b743720cea13_1679x615.png) ## 接口安全 > 前后端分离的开发方式,我们以接口为标准来进行推动,定义好接口,各自开发自己的功能,最后进行联调整合。无论是开发原生的APP还是webapp还是PC端的软件,只要是前后端分离的模式,就避免不了调用后端提供的接口来进行业务交互。网页或者app,只要抓下包就可以清楚的知道这个请求获取到的数据,这样的接口对爬虫工程师来说是一种福音,要抓你的数据简直轻而易举。数据的安全性非常重要,特别是用户相关的信息,稍有不慎就会被不法分子盗用,所以我们对这块要非常重视,容不得马虎。 ![](https://img.kancloud.cn/a1/fd/a1fd70159d8f54f37a249fc2c147cf08_675x302.png) * 3DES 3DES,也称为 3DESede 或 TripleDES,是三重数据加密算法,相当于是对每个数据库应用三次DES的对称加密算法。由于DES密码长度容易被暴力破解,所以3DES算法通过对DES算法进行改进,增加DES的密钥长度来避免类似的攻击,针对每个数据块进行三次DES加密;因此,3DES加密算法并非什么新的加密算法,是DES的一个更安全的变形,它以DES为基本模块,通过组合分组方法设计出分组加密算法。3DES是DES向AES过渡的加密算法,它使用2个或者3个56位的密钥对数据进行三次加密。相比DES,3DES因密钥长度变长,安全性有所提高,但其处理速度不高。因此又出现了AES加密算法,AES较于3DES速度更快、安全性更高。 ![](https://img.kancloud.cn/1c/7f/1c7fc6a2140235e0c798481d736e4163_1769x887.png) ### 样例 客户端携带参数appId、timestamp、sign去调用服务器端的API token,其中sign=加密(appId + timestamp + signkey) ![](https://img.kancloud.cn/f5/40/f540eac5d57c4f2bfaf10d48edc7c830_1723x621.png) {"data":"atCwUKkP6CUpPoNX+fyWJVSq7GA4QDfPYthOKqICCsnlyCe4wW8JezWWaP3PtsqG9MvISylslnEo\\r\\nc7a8aSe1GAkDWNl4ejqlNpGeHQ3NG1WTNcwvx4XCaw==","sign":"328ed5b34824197047c286868cb79fdd","appId":"zwwtest","timestamp":"1591877695909"} ![](https://img.kancloud.cn/1b/9c/1b9c837e39ec377de5cc607c73b81886_692x540.png) ## 代码安全扫描 ![](https://img.kancloud.cn/cf/58/cf58003de5ca3bfff8406a896c46e723_558x252.png) ![](https://img.kancloud.cn/f3/e4/f3e48d80b709e500cc42e4f3f8b07bdb_1958x806.png)