场景:一次迭代在灰度环境发版时,测试反馈说我开发的那个功能,查询接口有部分字段数据是空的,后续排查日志,发现日志如下:

feign.RetryableException: cannot retry due to redirection, in streaming mode executing POST

下面是业务、环境和分析过程下面是业务、环境和分析过程:

接口的业务场景 :我这个接口类似是那种报表统计的接口,它会请求多个微服务,把请求到的数据,统一返回给前端,相当于设计模式中的门面模式了。

后续由于这个接口 是串行请求其他微服务的,速度有些慢,后面修改代码从串行请求,改成并行(多线程)获取数据

运维那边是通过判断http请求中cookie 或者 header中的某个数据,来区分请求是否要把流量打到灰度。

分析得出:应该是接口异步请求的时候cookie丢失,没走到灰度环境,找不到 这次迭代新开发的接口,导致的重定向到错误页面了。

验证:由于我代码是通过@Async异步注解,实现并行请求的,临时把五个接口的异步注解注释掉了,灰度在发版验证,数据能返回正常,说明流量打到灰度了

说明问题就是并发请求的时候,子线程获取不到 主线程的request 头信息,导致没有走到灰度

下图就是灰度环境的 流程图:


问题定位出来了,解决方案就是:让子线程能获取到主线程的 request 头信息,主线程把 数据透传到子线程。

我使用的是RequestContextHolder来透传数据

什么是 RequestContextHolder?

RequestContextHolder 是spring mvc的一个工具类,顾名思义,持有上下文的Request容器

如何使用:

//获取当前线程 request请求的属性

RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();

//设置当前线程 request请求的属性

RequestContextHolder.setRequestAttributes(attributes);

RequestContextHolder的 会用到的几个方法

  1. currentRequestAttributes:获得当前线程请求的属性(头信息之类的)
  2. setRequestAttributes(attributes):设置当前线程 属性(设置头信息)
  3. resetRequestAttributes:删除当前线程 绑定的属性

下面是他们的源码,可以简单看一下,原理是通过ThreadLocal来绑定数据的:

  1. private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
  2. new NamedThreadLocal<>("Request attributes");
  3. private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
  4. new NamedInheritableThreadLocal<>("Request context");
  5. //获得当前线程请求的属性(头信息之类的)
  6. @Nullable
  7. public static RequestAttributes getRequestAttributes() {
  8. RequestAttributes attributes = requestAttributesHolder.get();
  9. if (attributes == null) {
  10. attributes = inheritableRequestAttributesHolder.get();
  11. }
  12. return attributes;
  13. }
  14. //设置当前线程 属性(设置头信息)
  15. public static void setRequestAttributes(@Nullable RequestAttributes attributes) {
  16. setRequestAttributes(attributes, false);
  17. }
  18. //设置当前线程 属性(设置头信息)
  19. public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
  20. if (attributes == null) {
  21. resetRequestAttributes();
  22. }
  23. else {
  24. if (inheritable) {
  25. inheritableRequestAttributesHolder.set(attributes);
  26. requestAttributesHolder.remove();
  27. }
  28. else {
  29. requestAttributesHolder.set(attributes);
  30. inheritableRequestAttributesHolder.remove();
  31. }
  32. }
  33. }
  34. //删除当前线程 绑定的属性
  35. public static void resetRequestAttributes() {
  36. requestAttributesHolder.remove();
  37. inheritableRequestAttributesHolder.remove();
  38. }

下面我编写了一套遇到问题的代码例子,以及解决的代码:

TestUserController

测试接口

  1. @Slf4j
  2. @RestController
  3. @RequestMapping(value = "/v1/testUser")
  4. public class TestUserController {
  5. @Autowired
  6. ITestRequestService testRequestService;
  7. @ApiOperation(value = "聚合数据接口(一)-串行获取数据")
  8. @RequestMapping(value = "/listUser", method = RequestMethod.GET)
  9. public Resp<List<User>> listUser(@RequestHeader(value = "token",required = false)String token){
  10. TimeInterval timeInterval = DateUtil.timer();
  11. DataResp dataResp = testRequestService.getDateResp();
  12. log.info("聚合数据接口(一)-串行获取数据 总耗时:{}毫秒",timeInterval.interval());
  13. return Resp.buildDataSuccess(dataResp).setTimeInterval(timeInterval.interval());
  14. }
  15. @ApiOperation(value = "聚合数据接口(二)-并行获取数据@Async (子线程获取不到token)")
  16. @RequestMapping(value = "/listUser2", method = RequestMethod.GET)
  17. public Resp<List<User>> listUser2(@RequestHeader(value = "token",required = false)String token) throws ExecutionException, InterruptedException {
  18. TimeInterval timeInterval = DateUtil.timer();
  19. DataResp dataResp = testRequestService.getDateResp2();
  20. log.info("聚合数据接口(二)-并行获取数据@Async (子线程获取不到token) 总耗时:{}毫秒",timeInterval.interval());
  21. return Resp.buildDataSuccess(dataResp).setTimeInterval(timeInterval.interval());
  22. }
  23. @ApiOperation(value = "聚合数据接口(三)-并行获取数据(子线程能获取到token)")
  24. @RequestMapping(value = "/listUser3", method = RequestMethod.GET)
  25. public Resp<List<User>> listUser3(@RequestHeader(value = "token",required = false)String token) throws ExecutionException, InterruptedException {
  26. TimeInterval timeInterval = DateUtil.timer();
  27. DataResp dataResp = testRequestService.getDateResp3();
  28. log.info("聚合数据接口(三)-并行获取数据(子线程能获取到token) 总耗时:{}毫秒",timeInterval.interval());
  29. return Resp.buildDataSuccess(dataResp).setTimeInterval(timeInterval.interval());
  30. }
  31. }

TestRequestService

聚合数据的类

  1. @Service
  2. public class TestRequestService implements ITestRequestService {
  3. @Autowired
  4. IUserService userService;
  5. @Autowired
  6. IOrderService orderService;
  7. /**
  8. * 自定义 - 线程池
  9. */
  10. private static final ThreadPoolExecutor executorService = new ThreadPoolExecutor(50, 200,
  11. 180L, TimeUnit.SECONDS,
  12. new LinkedBlockingQueue<Runnable>(3000), new ThreadFactory() {
  13. final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
  14. @Override
  15. public Thread newThread(Runnable r) {
  16. Thread thread = defaultFactory.newThread(r);
  17. thread.setName("testRequest - " + thread.getName());
  18. return thread;
  19. }
  20. }, new ThreadPoolExecutor.CallerRunsPolicy());
  21. /**
  22. * 聚合接口-串行获取数据
  23. * @return
  24. */
  25. @Override
  26. public DataResp getDateResp(){
  27. //获取用户列表
  28. List<User> userList = userService.listUser_1();
  29. //获取订单列表
  30. List<Order> orderList = orderService.listOrder_1();
  31. return DataResp.builder().userList(userList).orderList(orderList).build();
  32. };
  33. /**
  34. * 聚合接口-并行获取数据(@Async) 头信息传到子线程
  35. * @return
  36. */
  37. @Override
  38. public DataResp getDateResp2() throws ExecutionException, InterruptedException {
  39. //获取用户列表 start
  40. Future<List<User>> userListFuture = userService.listUser_2();
  41. List<User> userList = userListFuture.get();
  42. //获取用户列表 end
  43. //获取订单列表 start
  44. Future<List<Order>> orderListFuture = orderService.listOrder_2();
  45. List<Order> orderList = orderListFuture.get();
  46. //获取订单列表 end
  47. return DataResp.builder().userList(userListFuture.get()).orderList(orderList).build();
  48. };
  49. /**
  50. * 聚合接口-并行获取数据
  51. * @return
  52. */
  53. @Override
  54. public DataResp getDateResp3() throws ExecutionException, InterruptedException {
  55. RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
  56. //获取用户列表 start
  57. Future<List<User>> userListFuture = CompletableFuture.supplyAsync(() -> {
  58. RequestContextHolder.setRequestAttributes(attributes);
  59. try {
  60. List<User> resp = userService.listUser_3();
  61. return resp;
  62. }finally {
  63. RequestContextHolder.resetRequestAttributes();
  64. }
  65. }, executorService);
  66. List<User> userList = userListFuture.get();
  67. //获取用户列表 end
  68. //获取订单列表 start
  69. Future<List<Order>> orderListFuture = CompletableFuture.supplyAsync(() -> {
  70. RequestContextHolder.setRequestAttributes(attributes);
  71. try {
  72. List<Order> resp = orderService.listOrder_3();
  73. return resp;
  74. }finally {
  75. RequestContextHolder.resetRequestAttributes();
  76. }
  77. }, executorService);
  78. List<Order> orderList = orderListFuture.get();
  79. //获取订单列表 end
  80. return DataResp.builder().userList(userListFuture.get()).orderList(orderList).build();
  81. };
  82. }

下面是两个请求 用户和订单请求类

OrderService 请求订单的服务的聚合方法

  1. @Slf4j
  2. @Service
  3. public class OrderService implements IOrderService {
  4. /**
  5. * 获取订单code列表
  6. * @return
  7. */
  8. @Override
  9. public List<String> listOrderCode(){
  10. //使用httpUtil 模拟 feign请求服务接口 start
  11. String reqUrl = Config.baseUrl.concat("/v1/order/list");
  12. HttpRequest httpRequest = HttpUtil.createGet(reqUrl);
  13. //设置请求头信息
  14. String token = WebUtil.getCurrentRequestHeaderToken();
  15. httpRequest.header("token",token);
  16. HttpResponse httpResponse = httpRequest.execute();
  17. String body = httpResponse.body();
  18. Resp<List<String>> respData = JSONUtil.toBean(body, Resp.class);
  19. //使用httpUtil 模拟 feign请求服务接口 end
  20. if(respData.isSuccess()){
  21. return respData.getData();
  22. }
  23. return null;
  24. };
  25. /**
  26. * 根据订单code获取 订单数据
  27. * @param orderCode
  28. * @return
  29. */
  30. @Override
  31. public Order getOrder(String orderCode){
  32. //使用httpUtil 模拟 feign请求服务接口 start
  33. String reqUrl = StrUtil.format(Config.baseUrl.concat("/v1/order/get?orderCode={}"),orderCode);
  34. HttpRequest httpRequest = HttpUtil.createGet(reqUrl);
  35. //设置请求头信息
  36. String token = WebUtil.getCurrentRequestHeaderToken();
  37. httpRequest.header("token",token);
  38. HttpResponse httpResponse = httpRequest.execute();
  39. String body = httpResponse.body();
  40. Gson gson = new Gson();
  41. Resp<Order> respData = gson.fromJson(body , new TypeToken<Resp<Order>>(){}.getType());
  42. //使用httpUtil 模拟 feign请求服务接口 end
  43. if(respData.isSuccess()){
  44. return respData.getData();
  45. }
  46. return null;
  47. };
  48. /**
  49. * 获取订单列表(串行获取)
  50. * @return
  51. */
  52. @Override
  53. public List<Order> listOrder_1(){
  54. //获取订单列表 start
  55. List<Order> orderList = new ArrayList<>();
  56. List<String> orderCodes = listOrderCode();
  57. orderCodes.stream().forEach(orderCode->{
  58. Order order = getOrder(orderCode);
  59. orderList.add(order);
  60. });
  61. //获取订单列表 end
  62. return orderList;
  63. };
  64. /**
  65. * 获取订单列表(并行获取数据)
  66. * stream也改成了parallelStream 并行for循环
  67. * @return
  68. */
  69. @Async
  70. @Override
  71. public Future<List<Order>> listOrder_2(){
  72. log.info("listOrder_2 当前线程是:{}",Thread.currentThread().getName());
  73. //获取订单列表 start
  74. List<Order> orderList = new ArrayList<>();
  75. List<String> orderCodes = listOrderCode();
  76. if(CollUtil.isNotEmpty(orderCodes)){
  77. orderCodes.parallelStream().forEach(orderCode->{
  78. Order order = getOrder(orderCode);
  79. if(order!=null){
  80. orderList.add(order);
  81. }
  82. });
  83. }
  84. //获取订单列表 end
  85. return new AsyncResult<List<Order>>(orderList);
  86. };
  87. /**
  88. * 获取订单列表(并行获取数据)(把主线程的request的数据 透传给 子线程和子子线程)
  89. * @return
  90. */
  91. @Override
  92. public List<Order> listOrder_3(){
  93. //获取订单列表 start
  94. List<Order> orderList = new ArrayList<>();
  95. List<String> orderCodes = listOrderCode();
  96. if(CollUtil.isNotEmpty(orderCodes)){
  97. RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
  98. orderCodes.parallelStream().forEach(orderCode->{
  99. RequestContextHolder.setRequestAttributes(attributes);
  100. try {
  101. Order order = getOrder(orderCode);
  102. if(order!=null){
  103. orderList.add(order);
  104. }
  105. }finally {
  106. RequestContextHolder.resetRequestAttributes();
  107. }
  108. });
  109. }
  110. //获取订单列表 end
  111. return orderList;
  112. };
  113. }

UserService 请求订单的服务的聚合方法

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. import java.util.concurrent.Future;
  4. @Slf4j
  5. @Service
  6. public class UserService implements IUserService {
  7. @Override
  8. public List<Integer> listUserId(){
  9. //使用httpUtil 模拟 feign请求服务接口 start
  10. String reqUrl = Config.baseUrl.concat("/v1/user/list");
  11. HttpRequest httpRequest = HttpUtil.createGet(reqUrl);
  12. //设置请求头信息
  13. String token = WebUtil.getCurrentRequestHeaderToken();
  14. httpRequest.header("token",token);
  15. HttpResponse httpResponse = httpRequest.execute();
  16. String body = httpResponse.body();
  17. Resp<List<Integer>> respData = JSONUtil.toBean(body, Resp.class);
  18. //使用httpUtil 模拟 feign请求服务接口 end
  19. if(respData.isSuccess()){
  20. return respData.getData();
  21. }
  22. return null;
  23. };
  24. @Override
  25. public User getUser(Integer userId){
  26. //使用httpUtil 模拟 feign请求服务接口 start
  27. String reqUrl = StrUtil.format(Config.baseUrl.concat("/v1/user/get?userId={}"),userId);
  28. HttpRequest httpRequest = HttpUtil.createGet(reqUrl);
  29. //设置请求头信息
  30. String token = WebUtil.getCurrentRequestHeaderToken();
  31. httpRequest.header("token",token);
  32. HttpResponse httpResponse = httpRequest.execute();
  33. String body = httpResponse.body();
  34. Gson gson = new Gson();
  35. Resp<User> respData = gson.fromJson(body , new TypeToken<Resp<User>>(){}.getType());
  36. //使用httpUtil 模拟 feign请求服务接口 end
  37. if(respData.isSuccess()){
  38. return respData.getData();
  39. }
  40. return null;
  41. };
  42. @Override
  43. public List<User> listUser_1(){
  44. //获取用户列表 start
  45. List<User> userList = new ArrayList<>();
  46. List<Integer> userIds = listUserId();
  47. userIds.stream().forEach(userId->{
  48. User user = getUser(userId);
  49. userList.add(user);
  50. });
  51. //获取用户列表 end
  52. return userList;
  53. };
  54. @Async
  55. @Override
  56. public Future<List<User>> listUser_2(){
  57. log.info("listUser_2 当前线程是:{}",Thread.currentThread().getName());
  58. //获取用户列表 start
  59. List<User> userList = new ArrayList<>();
  60. List<Integer> userIds = listUserId();
  61. if(CollUtil.isNotEmpty(userIds)){
  62. userIds.parallelStream().forEach(userId->{
  63. User user = getUser(userId);
  64. if(user!=null){
  65. userList.add(user);
  66. }
  67. });
  68. }
  69. //获取用户列表 end
  70. return new AsyncResult<List<User>>(userList);
  71. };
  72. @Override
  73. public List<User> listUser_3(){
  74. //获取用户列表 start
  75. List<User> userList = new ArrayList<>();
  76. List<Integer> userIds = listUserId();
  77. if(CollUtil.isNotEmpty(userIds)){
  78. RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
  79. userIds.parallelStream().forEach(userId->{
  80. RequestContextHolder.setRequestAttributes(attributes);
  81. try {
  82. User user = getUser(userId);
  83. if(user!=null){
  84. userList.add(user);
  85. }
  86. }finally {
  87. RequestContextHolder.resetRequestAttributes();
  88. }
  89. });
  90. }
  91. //获取用户列表 end
  92. return userList;
  93. };
  94. }

OrderController 你可以理解成其他其他微服务的接口(模拟写的一个接口,用来测试 请求接口的时候是否携带 请求头了)

  1. @Slf4j
  2. @RestController
  3. @RequestMapping(value = "/v1/order")
  4. public class OrderController {
  5. @ApiOperation(value = "获取订单编号列表")
  6. @RequestMapping(value = "/list", method = RequestMethod.GET)
  7. public Resp<List<String>> list(HttpServletRequest request){
  8. String token = request.getHeader("token");
  9. if(StrUtil.isBlank(token)){
  10. return Resp.buildFail("接口不存在 404");
  11. }
  12. List<String> userIds = new ArrayList<>();
  13. userIds.add("11111");
  14. userIds.add("22222");
  15. userIds.add("33333");
  16. userIds.add("44444");
  17. userIds.add("55555");
  18. userIds.add("6666");
  19. userIds.add("7777");
  20. handleBusinessTime();
  21. return Resp.buildDataSuccess(userIds);
  22. }
  23. @ApiOperation(value = "获取订单详情")
  24. @ApiImplicitParams({
  25. @ApiImplicitParam(name = "orderCode", value = "订单CODE", paramType = "query"),
  26. })
  27. @RequestMapping(value = "/get", method = RequestMethod.GET)
  28. public Resp<Order> get(HttpServletRequest request,@RequestParam(value = "orderCode")String orderCode){
  29. String token = request.getHeader("token");
  30. if(StrUtil.isBlank(token)){
  31. return Resp.buildFail("接口不存在 404");
  32. }
  33. handleBusinessTime();
  34. String name = StrUtil.format("订单-{}-名",orderCode);
  35. return Resp.buildDataSuccess(Order.builder().code(orderCode).orderName(name).build());
  36. }
  37. /**
  38. * 这方法 模拟处理业务或者 去操作数据库 消耗的时间
  39. */
  40. public static void handleBusinessTime(){
  41. //去数据库查询数据耗时 start
  42. int[] sleepTime = NumberUtil.generateRandomNumber(300,800,1);
  43. try {
  44. //Thread.sleep 休眠的时候 相当于 业务操作,或者请求数据库的需要消耗的时间
  45. Thread.sleep(sleepTime[0]);
  46. } catch (InterruptedException e) {
  47. e.printStackTrace();
  48. }
  49. //去数据库查询数据耗时 end
  50. }
  51. }
  1. @Slf4j
  2. @RestController
  3. @RequestMapping(value = "/v1/user")
  4. public class UserController {
  5. @ApiOperation(value = "获取用户列表-id")
  6. @ApiImplicitParams({
  7. @ApiImplicitParam(name = "orderCode", value = "订单编号", paramType = "query"),
  8. })
  9. @RequestMapping(value = "/list", method = RequestMethod.GET)
  10. public Resp<List<Integer>> list(HttpServletRequest request){
  11. String token = request.getHeader("token");
  12. if(StrUtil.isBlank(token)){
  13. return Resp.buildFail("接口不存在 404");
  14. }
  15. List<Integer> userIds = new ArrayList<>();
  16. userIds.add(1);
  17. userIds.add(2);
  18. userIds.add(3);
  19. userIds.add(4);
  20. userIds.add(5);
  21. handleBusinessTime();
  22. return Resp.buildDataSuccess(userIds);
  23. }
  24. @ApiOperation(value = "根据用户ID获取 用户信息")
  25. @ApiImplicitParams({
  26. @ApiImplicitParam(name = "userId", value = "用户ID", paramType = "query"),
  27. })
  28. @RequestMapping(value = "/get", method = RequestMethod.GET)
  29. public Resp<User> get(HttpServletRequest request,@RequestParam(value = "userId")Integer userId){
  30. String token = request.getHeader("token");
  31. if(StrUtil.isBlank(token)){
  32. return Resp.buildFail("接口不存在 404");
  33. }
  34. handleBusinessTime();
  35. String name = StrUtil.format("用户{}号",userId);
  36. return Resp.buildDataSuccess(User.builder().id(userId).name(name).build());
  37. }
  38. /**
  39. * 这方法 模拟处理业务或者 去操作数据库 消耗的时间
  40. */
  41. public static void handleBusinessTime(){
  42. //去数据库查询数据耗时 start
  43. int[] sleepTime = NumberUtil.generateRandomNumber(300,800,1);
  44. try {
  45. //Thread.sleep 休眠的时候 相当于 业务操作,或者请求数据库的需要消耗的时间
  46. Thread.sleep(sleepTime[0]);
  47. } catch (InterruptedException e) {
  48. e.printStackTrace();
  49. }
  50. //去数据库查询数据耗时 end
  51. }
  52. }

下面三个接口的由来:

  1. /v1/testUser/listUser 接口:就是串行调用其他服务接口 ,性能比较慢
  2. /v1/testUser/listUser2 接口:是通过@Async 异步注解,并行调用其他 系统的接口,性能是提升上去了,但灰度环境 是需要根据请求头里面的数据判断是否把流量打到灰度环境
  3. /v1/testUser/listUser3接口:对@Async注解没有找到透传 主线程request头信息的方案,就使用线程池+CompletableFuture.supplyAsync的方式 每次执行异步线程的时候,把主线程的 请求参数设置到子线程,然后通过try-finally 参数使用完之后RequestContextHolder.resetRequestAttributes() 删除参数。

注意:parallelStream它也是属于并行流操作,也要设置 请求头信息,虽说子线程(getDateResp3方法)能获取到主线程的请求头信息了,但是parallelStream 又相当于子线程的子线程了,它是获取不到的 主线程的attributes的,当时我就是没在parallelStream设置attributes,它没有走到灰度环境, 让我 耗费了两个多小时,代码加了四五次日志输出,才把这个问题定位出来,这是一个坑。。。

下面是代码:

基于这个问题,我还写了一篇 spring boot使用@Async的文章,大家感兴趣可以去看看 传送门~

我已经把上述代码例子放到gitee了,大家感兴趣可以clone 传送门~

spring boot 并发请求,其他系统接口,丢失request的header信息【多线程、线程池、@Async 】的更多相关文章

  1. Spring Boot的数据访问:CrudRepository接口的使用

    示例 使用CrudRepository接口访问数据 创建一个新的Maven项目,命名为crudrepositorytest.按照Maven项目的规范,在src/main/下新建一个名为resource ...

  2. Spring Boot启动过程及回调接口汇总

    Spring Boot启动过程及回调接口汇总 链接: https://www.itcodemonkey.com/article/1431.html 来自:chanjarster (Daniel Qia ...

  3. spring boot使用swagger生成api接口文档

    前言 在之前的文章中,使用mybatis-plus生成了对应的包,在此基础上,我们针对项目的api接口,添加swagger配置和注解,生成swagger接口文档 具体可以查看本站spring boot ...

  4. Spring Boot 异步请求和异步调用,一文搞定

    一.Spring Boot中异步请求的使用 1.异步请求与同步请求 特点: 可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如 ...

  5. Spring Boot AOP 扫盲,实现接口访问的统一日志记录

    AOP 是 Spring 体系中非常重要的两个概念之一(另外一个是 IoC),今天这篇文章就来带大家通过实战的方式,在编程猫 SpringBoot 项目中使用 AOP 技术为 controller 层 ...

  6. Spring Boot 集成 Swagger,生成接口文档就这么简单!

    之前的文章介绍了<推荐一款接口 API 设计神器!>,今天栈长给大家介绍下如何与优秀的 Spring Boot 框架进行集成,简直不能太简单. 你所需具备的基础 告诉你,Spring Bo ...

  7. Spring Boot使用AOP实现REST接口简易灵活的安全认证

    我们继续上一篇文章的分析,本文将通过AOP的方式实现一个相对更加简易灵活的API安全认证服务. 我们先看实现,然后介绍和分析AOP基本原理和常用术语. 一.Authorized实现 1.定义注解 pa ...

  8. Java 后台请求第三方系统接口详解

    //调用第三方系统接口 PrintWriter out = null; BufferedReader in = null; JSONObject jsonObject = null; Closeabl ...

  9. 基于Spring Boot的在线问卷调查系统的设计与实现+论文

    全部源码下载 # 基于Spring Boot的问卷调查系统 ## 介绍 > * 本项目的在线问卷调查调查系统是基于Spring Boot 开发的,采用了前后端分离模式来开发. > * 前端 ...

随机推荐

  1. 2019c#将PDF转图片

    两种方法: 第一种是用O2S.Components.PDFRender4NET 大家可以去网上查找无水印版本 但是有的时候带颜色的字就变空白了 不知道为什么 第二种是用PdfiumViewer 这种方 ...

  2. x265 code tracing

    方瑞东的博客专栏 http://blog.csdn.net/frd2009041510/article/details/51182920 cabbage2008的专栏 http://blog.csdn ...

  3. HEVC学习(一) —— HM的使用

    http://blog.csdn.net/hevc_cjl/article/details/8169182 首先自然是先把这个测试模型下载下来,链接地址如下:https://hevc.hhi.frau ...

  4. springboot+Thymeleaf+layui 实现分页

    layui分页插件 引入相关的js和css layui:css <link rel="stylesheet" th:href="@{layui/css/layui. ...

  5. 把el-element的日期格式改为CRON

    在日常的开发当中,经常会遇到格式的不匹配造成的困扰. 在日期管理上,el-element也是贴心的准备了相关的日期选择器,但是在取值的时候发现,el-element所给出的值格式可能并不是我们常用的. ...

  6. CentOS、RHEL、Asianux、Neokylin、湖南麒麟、BC Linux、普华、EulerOS请参考“1.1 CentOS本地源配置”;

      本文档适用于CentOS.RHEL.Asianux.Neokylin.湖南麒麟.BC Linux.普华.EulerOS.SLES.Ubuntu.Deepin.银河麒麟. CentOS.RHEL.A ...

  7. Docker Swarm(六)Label 节点标签与服务约束

    前言 多节点 Swarm 集群下,可能节点的配置不同(比如 CPU.内存等),部署着不同类型的服务(比如 Web服务.Job服务等),当这些服务以 Service 或者 Stack 的形式部署到集群, ...

  8. Linux进阶之补充知识篇

    一.Linux系统的主要特点: 开放性:指系统遵循世界标准规范,特别是遵循开放系统互连(OSI)国际标准 多用户:允许多个用户从相同或不同终端上同时使用同一台计算机 多任务:它是指计算机同时执行多个程 ...

  9. 11.4 iftop:动态显示网络接口流量信息

    iftop是一款实时流量监控工具,可用于监控TCP/IP连接等,必须以root用户的身份运行. 一般最小化安装系统都是没有这个命令的,需要使用yum命令额外安装,而且还要从epel源下载.   ift ...

  10. Oracle和MySQL差异总结

    常用功能差异 锁差异: • Oracle锁加在数据块上 • InnoDB 是在索引上加锁,所以MySQL锁的粒度没有Oracle 精细. 导入导出: • Oracle采用EXP /IMP ,EXPDP ...