• [技术干货] @Fegin用法介绍 示例
    Feign是一个声明式的、基于接口的HTTP客户端,方便了开发者使用RESTful Web Services。它可以将Java接口转换为Web服务客户端,并自动与Spring Cloud服务注册中心集成,从而使得客户端可以通过服务名访问对应的远程服务。下面是@Feign用法的示例:添加Feign相关依赖包 xml复制代码<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> 创建一个接口定义服务 java复制代码@FeignClient(name = "service-provider") public interface UserFeignClient { @GetMapping("/user/{id}") User getUserById(@PathVariable("id") Long id); @PostMapping("/user") User addUser(@RequestBody User user); } 其中,@FeignClient注解用于指定服务提供方的服务名,而getUserById和addUser方法则定义了两个接口,分别用于获取用户和添加用户。通过@Autowired方式注入UserFeignClient接口 java复制代码@RestController public class UserController { @Autowired private UserFeignClient userFeignClient; @GetMapping("/user/{id}") public User getUserById(@PathVariable Long id) { return userFeignClient.getUserById(id); } @PostMapping("/user") public User addUser(@RequestBody User user) { return userFeignClient.addUser(user); } } 启动服务并测试API在启动服务之前,确保已经启动服务提供方("service-provider"),并且已经注册到了服务注册中心。然后,通过访问上述接口,即可实现通过服务名调用远程服务。总之,@Feign是一种声明式的、基于接口的HTTP客户端,可以方便地使用RESTful Web Services。在使用该注解时,需要定义一个接口并指定服务提供方的服务名,然后通过@Autowired方式将接口注入到其他类中,并实现对应的方法即可进行调用。
  • [技术干货] OpenFegin+hystrix实现远程HTTP服务调用、服务降级
     简介 OpenFeign 是 Spring Cloud 家族的一个成员, 它最核心的作用是为 HTTP 形式的 Rest API 提供了非常简洁高效的 RPC 调用方式。支持Hystrix 、Ribbon和SpringMVC 注解。  Feign和OpenFeign的区别? 1、Feign:  Feign是Netflix公司(第一代SpringCloud)研发的一个轻量级RESTful的伪HTTP服务客户端。  Feign内置了Ribbon逻辑,通过负载均衡算法可以从注册中心中寻找服务。  2、OpenFeign:  OpenFeign是SpringCloud自己研发的,在Feign的基础上做了增强。  OpenFeign除了原有Ribbon逻辑外,还支持了Hystrix和Spring MVC注解。  一、服务端Provider 注意: 当前是springboot整合OpenFegin 服务端没有配置注册中心, 如分布式 微服务请自行配置  服务端Controller层 @CrossOrigin @RestController @RequestMapping("/customer/information") public class CrmCustomersController {         @Autowired     private CrmCustomersService crmCustomersService;       /**      * 服务数据      */     @RequestMapping(value = "customerlist", method = {RequestMethod.GET,RequestMethod.POST})     public PmpResult  queryCustomerList (@RequestParam(value="pageNum",defaultValue="1") int pageNum, @RequestParam(value="pageSize",defaultValue="10") int pageSize,String customersno, String customersname,String daogouid){         try {               PageInfo<CustomerInformationVo> pageInfo = crmCustomersService.queryCustomerList(pageNum,pageSize,customersno,customersname,daogouid);               logger.info("查询成功");             return PmpResult.success(pageInfo);         }catch (Exception e) {             logger.error(e.getMessage(), e);             String errorMessage = "查询异常";             if (isDev){                 errorMessage = e.getMessage();             }             return PmpResult.paramError(errorMessage);         }       }    }  二、消费端Consumer 消费端~例图  Pom.xml <!-- openFeing --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>  Api @FeignClient注解  @FeignClient参数:  name: 指定FeignClient的(服务)名称,在Eureka/Nacos/其他注册中心 中对应的服务名称。  url: 指定远程调用地址。  configuration: 指定配置类,可以自定义FeignClient的配置。  fallback: 指定降级类,在服务调用失败时返回降级类中的内容。  fallbackFactory: 指定降级工厂类,在服务调用失败时返回降级工厂类中的内容。  decode404: 指定404响应是否解码,默认为true。  path: 指定服务请求的基础路径。  contextId: 当一个服务有多个接口时,我们又不想把所有接口都写到一个类中是时候就用到了 contextId为当前类设置一个唯一ID。不然就回报如下错误 。  [扩展]@FeignClient注解 通过属性 contextId 解决名称重复无法启动的问题 错误如下:  Description: The bean 'optimization-user.FeignClientSpecification', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled. Action: Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true 导致 名称重复问题 复现:  /* *Consumer1 */ @FeignClient(name = "xj-user") public interface UserRemoteConsumer1 {     @GetMapping("/user1/get")     public User getUser(@RequestParam("id") int id); }   /* *Consumer2 */ @FeignClient(name = "xj-user") public interface UserRemoteConsumer2 {     @GetMapping("/user2/get")     public User getUser(@RequestParam("id") int id); }  这种情况下启动就会报错了,因Bean的名称冲突。  原因:由于name重复,而又不允许BeanDefinition重复,所以导致在进行注册时报错。  解决方案一:  增加下面的配置,作用允许出现beanName一样的BeanDefinition。   spring.main.allow-bean-definition-overriding=true 解释:  spring.main.allow-bean-definition-overriding=true应用程序的一个配置属性,它允许在应用程序上下文中覆盖bean定义。如果设置为true,则可以在应用程序上下文中定义多个具有相同名称的bean,后定义的bean将覆盖先前定义的bean。但是,这可能会导致不可预测的行为和错误,因此应该谨慎使用。  解决方案二(推荐):  每个FeignClient手动指定不同的contextId,这样就不会冲突了。  @FeignClient添加contextId属性  /* *Consumer1 */ @FeignClient(name = "xj-user" contextId = UserRemoteConsumer1) public interface UserRemoteConsumer1 {     @GetMapping("/user1/get")     public User getUser(@RequestParam("id") int id); }   /* *Consumer2 */ @FeignClient(name = "xj-user" contextId = UserRemoteConsumer2) public interface UserRemoteConsumer2 {     @GetMapping("/user2/get")     public User getUser(@RequestParam("id") int id); }  总结:想创建多个具有相同名称或url的外部客户端,以便它们指向同一台服务器,但每个服务器都有不同的自定义配置,那么可以使用@FeignClient的contextId属性,以避免这些配置bean的名称冲突。  @RequestMapping(value = "/customer/information/customerlist", method = RequestMethod.GET)是要调的接口名  openFegin调用如下:  import com.alibaba.fastjson.JSONObject; import com.lt.crm.service.fallback.TransactionFallBackFactory; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Repository; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam;     @Repository @FeignClient(name = "service-client", url = "http://10.1.8.22:9001", contextId = "ConsumerService", fallbackFactory = TransactionFallBackFactory.class) public interface ConsumerService {       @RequestMapping(value = "/customer/information/customerlist", method = RequestMethod.GET)     JSONObject queryCustomerList(@RequestParam(value = "pageNum") final int pageNum, @RequestParam(value = "pageSize") final int pageSize);     }  Hystrix 服务降级处理 说明:  Hystrix配合OpenFeign进行降级,可以对应接口中定义的远程调用单独进行降级操作。  直白的说: 远程调用失败,我们添加一个替代方案,我们知道OpenFegin是以接口的形式来声明远程调用,只要是远程调用失效超时,就执行替代方案。创建一个实现类,对原有的接口方法进行替代方案实现。  使用@Component注解 交给String管理 注入IOC容器  对Api接口进行进行实现、重写调用的Api 远程接口 代码如下:  @Component 注解是 Spring 框架中的注解,用于将一个类标记为 Spring 容器中的一个组件,让 Spring 自动扫描并管理这个组件的生命周期和依赖注入。  实现implements FallbackFactory原因:用于在使用 Feign 进行服务调用时,定义一个 FallbackFactory<> 来处理服务调用失败的情况的<ConsumerService>。[ import feign.hystrix.FallbackFactory; ]  以下码为例:服务降级内容我只是返回一段话,根据自己业务定性, 可判断参数是否为空等 返回具体错误信息。  import com.alibaba.fastjson.JSONObject; import com.lt.crm.service.api.ConsumerService; import feign.hystrix.FallbackFactory; import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component;     @Component public class TransactionFallBackFactory implements FallbackFactory<ConsumerService>  {       private Logger logger = LoggerFactory.getLogger(getClass());     @Override     public ConsumerService create(Throwable throwable) {           return new ConsumerService() {             @Override             public JSONObject queryCustomerList(int pageNum, int pageSize) {                 logger.error("TransactionMsgWaitingConfirm Error : {}" , ExceptionUtils.getFullStackTrace(throwable));                 String  fallbackMessage = "被Hystrix熔断,已作降级处理!";                 JSONObject jsonObject = new JSONObject();                 jsonObject.put("fallbackMessage",fallbackMessage);                 return jsonObject;             }         };     }       }  消费端Controller层 @Autowired  private ConsumerService consumerService;  在消费端Controller注入Api调用 ConsumerService    import com.alibaba.fastjson.JSONObject; import com.lt.crm.common.PmpResult; import com.lt.crm.service.api.ConsumerService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody;     @Controller @RequestMapping("/customer/information") @Api(tags = "跨服务调用数据") public class ConsumerController {     /**      * 日志      */     private final static Logger logger = (Logger) LoggerFactory.getLogger(ConsumerController.class);       @Autowired     private ConsumerService consumerService;       @ResponseBody     @ApiOperation(value = "公共开放的端口")     @RequestMapping(value = "customerlist", method = RequestMethod.GET)     public PmpResult queryCustomerList(@RequestParam(value = "pageNum", defaultValue = "1") int pageNum, @RequestParam(value = "pageSize", defaultValue = "10") int pageSize) {           try {              // String类型 转换成json 接收             JSONObject GetJsonObject = consumerService.queryCustomerList(pageNum, pageSize);                if (null != GetJsonObject) {                 logger.info("调用数据成功");                 return PmpResult.success("数据服务", GetJsonObject);             }           } catch (Exception e) {             e.printStackTrace();             return PmpResult.serviceError("服务异常");         }         return null;     }     }  application.properties配置超时时间 说明: OpenFegin 默认1ms 未响应会抛出异常 , 往往在调用远程接口的时候 、业务逻辑复杂、 没等业务处理完毕就抛出异常 、我们可以针对这类问题 可以配置延长时长。  配置 Feign 超时问题  配置hystrix 超时时间  ######################################openFegin###################################### # 默认开启 feign.httpclient.enabled=true # 默认关闭 feign.okhttp.enabled=false # 默认关闭 feign.hystrix.enabled=true # 默认关闭 feign.sentinel.enabled=false   # default context 连接超时时间 5000ms = 5秒 feign.client.config.default.connectTimeout = 5000 # default context 读超时时间 feign.client.config.default.readTimeout = 5000   ######################################Hystrix Config###################################### #开启hystrix超时管理 hystrix.command.default.execution.timeout.enabled=true #hystrix超时时间 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000   #开启ribbon超时管理 ribbon.http.client.enabled=false #请求超时时间 ribbon.ReadTimeout=20000 #连接超时时间 ribbon.ConnectTimeout=20000 ribbon.MaxAutoRetries=0 ribbon.MaxAutoRetriesNextServer=1 ribbon.OkToRetryOnAllOperations=false  启动类: 启动类上使用注解 @EnableFeignClients 开启 openFeign 功能。  /**  * 微服务应用服务启动类  * 1、(@EnableDiscoveryClient)注解为链接微服务注册中心用,如实际环境中使用注册中心,请取消注释部分,  *     与配置文件中相关注册中心配置信息结合使用。  * @author xj  *  */ @EnableDiscoveryClient @EnableFeignClients(clients= {ConsumerService.class} )//加入@EnableFeignClients注解 开启Feign远程服务调用,使Feign的bean可以被注入 @ServletComponentScan @SpringBootApplication(scanBasePackages = {"com.lt.crm"}) @EnableCaching public class OpenFeignApplication {     public static void main(String[] args) {         SpringApplication.run(OpenFeignApplication.class, args);} }  测试 熔断降级 启动消费端Consumer,并没有启动服务端Provider ,过了超时时间,此时执行了服务降级,这样做避免了在微服务中服务雪崩的灾难。  后台也捕获了error :  连接被拒绝:连接执行GET: Connection refused: connect executing GET http://10.1.8.22:9001/customer/information/customerlist?pageNum=1&pageSize=10  2023-03-17 16:56:52.399 ERROR 20928 --- [ervice-client-1] c.l.c.s.f.TransactionFallBackFactory     : TransactionMsgWaitingConfirm Error : feign.RetryableException: Connection refused: connect executing GET http://10.1.8.22:9001/customer/information/customerlist?pageNum=1&pageSize=10     at feign.FeignException.errorExecuting(FeignException.java:84)     at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:113)     at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:78)     at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:106)     at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302)     at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298)     at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46)     at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35)     at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)     at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)     at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)     at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)     at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)     at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)     at rx.Observable.unsafeSubscribe(Observable.java:10327)     at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:51)     at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35)     at rx.Observable.unsafeSubscribe(Observable.java:10327)     at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41)     at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30)     at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)     at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)     at rx.Observable.unsafeSubscribe(Observable.java:10327)     at rx.internal.operators.OperatorSubscribeOn$SubscribeOnSubscriber.call(OperatorSubscribeOn.java:100)     at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:56)     at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:47)     at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction.call(HystrixContexSchedulerAction.java:69)     at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)     at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)     at java.util.concurrent.FutureTask.run(FutureTask.java:266)     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)     at java.lang.Thread.run(Thread.java:748)  现在启动服务端再次调用,已经通过消费者9004端口 拿到了服务端9001提供的数据。  三、openFeign传参方式 简介:   在接口当中传参的方式有很多,但是在 openFeig中的 传参是 有一定规则的,详细如下。  传递JSON数据 服务端Provider接口中JSON传参方法如下:  @RestController @RequestMapping("/openfeign/provider") public class OpenFeignProviderController {     @PostMapping("/order1")     public Order createOrder1(@RequestBody Order order){         return order;     } } 注意:在Spring Boot 中通过@RequestBody标识入参。  消费端Consumer在openFeign接口传参如下:  @FeignClient(value = "openFeign-provider")//服务名 public interface OpenFeignService {     /**      * 参数默认是@RequestBody标注的,这里的@RequestBody可以不填      * 方法名称任意      */     @RequestMapping("/openfeign/provider/order1", method = RequestMethod.GET)     Order createOrder1(@RequestBody Order Order); } 注意: 我用的是@RequestMapping注解 这是一个组合注解,里面包含@RequestBody 可以不填。  POJO表单传参 服务端Provider接口传参方法如下:  @RestController @RequestMapping("/openfeign/provider") public class OpenFeignProviderController {     @PostMapping("/order2")     public Order createOrder2(Order order){         return order;     } } 注意: 参数使用POJO对象接收。  消费端Consumer在openFeign接口传参如下:  @FeignClient(value = "openFeign-provider") public interface OpenFeignService {     /**      * 参数默认是@RequestBody标注的,如果通过POJO表单传参的,使用@SpringQueryMap标注      */     @RequestMapping("/customer/information/order2", method = RequestMethod.GET)     Order createOrder2(@SpringQueryMap Order order); } 注意: openFeign提供了一个注解@SpringQueryMap解决POJO表单传参,这也是官方文档明确给出了解决方案。  URL中携带参数 服务端Provider接口传参方法如下:  @RestController @RequestMapping("/openfeign/provider") public class OpenFeignProviderController {       @GetMapping("/test/{id}")     public String test(@PathVariable("id")Integer id){         return "accept one msg id="+id; } 注意: 此种方式针对restful方式中的GET请求方式。  消费端Consumer在openFeign接口传参如下:  @FeignClient(value = "openFeign-provider") public interface OpenFeignService {       @GetMapping("/openfeign/provider/test/{id}")     String get(@PathVariable("id")Integer id); } 注意: 使用注解@PathVariable接收url中的占位符。  四、开启日志增强 说明: openFeign 虽提供了日志增强功能,但默认是不显示任何日志的,开发者在调试阶段可以自己配置日志的级别。  级别如下:  NONE:默认的,不显示任何日志;  BASIC:仅记录请求方法、URL、响应状态码及执行时间;  HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;  FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。  配置日志级别 自定义一个配置类,在其中设置日志级别如下:    import feign.Logger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;   @Configuration public class OpenFeginConfig {     /**      * 日志级别定义      * @return      */     @Bean     Logger.Level OpenFeignLoggerLevel(){         return Logger.Level.FULL;     } }  注意细节:Logger导包是feign包。  YAML配置 logging:   level:     com.lt.crm.service.api: debug 注意:此处 com.lt.crm.service.api 是 openFeign 接口所在的包名,可以根据自己项目路径配置一个特定的openFeign接口。  注解@FeignClient中配置 configuration 说明:@FeignClient配置里 configuration = OpenFeginConfig.class  @Repository @FeignClient(name = "service-client", url = "http://10.1.8.22:9001", contextId = "ConsumerService", fallbackFactory = TransactionFallBackFactory.class, configuration = OpenFeginConfig.class) public interface ConsumerService {       @RequestMapping(value = "/customer/information/customerlist", method = RequestMethod.GET)     JSONObject queryCustomerList(@RequestParam(value = "pageNum") final int pageNum, @RequestParam(value = "pageSize") final int pageSize);     } 查看打印日志示意图  五、Gzip压缩数据(全局压缩~接口与浏览器响应压缩) 全局压缩相关配置 ######################################接口与浏览器响应压缩###################################### #全局压缩(接口与浏览器响应压缩) server:   compression:     #开启Gzip压缩     enabled: true     #配置min-response-size 压缩数据大小的最小阈值,默认2048     min-response-size: 1 #设置1kb以上开始压缩     #配置mime-types 压缩支持的MIME  TYPE     mime-types:     - image/png     - image/jpeg     - image/jpg     - text/html     - text/xml     - application/xml     - application/json  min-response-size 和 mime-types 含默认值,可以不手动指定,如有其他需要可按需指定  测试压缩 未开启压缩前 请求数据大小为526KB  开启压缩 请求数据大小为82KB  点击查看请求头信息  Gzip压缩算法 Gzip是一种数据格式,采用deflate算法压缩数据;当GZIP算法压缩到一个纯文本数据时,效果会非常显著。  数据经过压缩后实际上降低了网络传输的字节数,最明显的好处就是可以加快网页加载的速度。网页加载速度加快,除了节省流量,改善用户的浏览体验外。  Gzip压缩传输原理图如下:  1、客户端向服务器请求头中带有:Accept-Encoding:gzip,deflate 字段,向服务器表示,客户端支持的压缩格式(gzip或者deflate),如果不发送该消息头,服务器是不会压缩的。  2、服务端在收到请求之后,如果发现请求头中含有 Accept-Encoding 字段,并且支持该类型的压缩,就对响应报文压缩之后返回给客户端,并且携带 Content-Encoding:gzip 消息头,表示响应报文是根据该格式压缩过的。  3、客户端接收到响应之后,先判断是否有 Content-Encoding 消息头,如果有,按该格式解压报文。否则按正常报文处理。  六、OpenFeign开启Gzip压缩数据(局部压缩~微服务之间利用Feign请求及响应压缩) 全局压缩相关配置 ######################################openFegin Gzip###################################### #局部压缩(微服务之间利用feign请求及响应压缩) feign:   compression:     request:       enabled: true    # 开启压缩       min-request-size: 1  # 开启压缩的阈值,单位字节,默认2048,即是2k,此处为演示效果设置成1字节       mime-types: text/xml,application/xml,application/json     response:       enabled: true #响应压缩 注意:openFeign支持的Gzip仅仅是在openFeign接口的请求和响应,即openFeign消费者调用服务提供者的接口。  开启Gzip压缩后日志信息 查看Gzip压缩开启后的打印日志  日志中包含 content-encoding: gzip 表示压缩成功  开启Gzip压缩后日志信息 查看Gzip压缩关闭后的打印日志  日志中未出现 content-encoding: gzip 表示未被压缩 并且可以清楚看到时间为 1231ms  七、如何OpenFegin 替换默认的httpclient ? 前言:  1、OpenFeign在默认情况下使用的是JDK原生的URLConnection发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection。  2、默认的http客户端是 javax.net.ssl.HttpsURLConnection,详细信息查看feign-core:feign.Client,该http客户端不支持添加拦截器和连接池。所以我们需要添加第三方http客户端。  在生产环境中,通常也不会使用默认的httpclient,两种选择如下:  使用ApacheHttpClient  使用OkHttp  声明: 我使用的是 OkHttp 如果使用 ApacheHttpClient 思路是基本一致 。  pom.xml依赖如下         <!-- 使用 Apache HttpClient 替换 Feign原生httpclient-->         <dependency>             <groupId>org.apache.httpcomponents</groupId>             <artifactId>httpclient</artifactId>         </dependency>         <!-- 使用 Okhttp 替换 Feign原生httpclient-->         <dependency>             <groupId>io.github.openfeign</groupId>             <artifactId>feign-okhttp</artifactId>             <version>10.1.0</version>         </dependency> application.properties 启用 OkHttp 配置 # 默认开启 feign.httpclient.enabled=false # 默认关闭 feign.okhttp.enabled=true 关闭默认的httpclient 开启okhttp  配置OkHttp响应拦截器 前言:  微服务之间使用OpenFeign,由于业务得需求有时候我们需要在请求或者响应的时候做一些额外的操作。比如请求的时候添加请求头,响应时候判断token是否过期等等。这时候拦截器就派上用场了! import lombok.extern.slf4j.Slf4j; import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.io.IOException;   @Slf4j @Configuration public class FeignOkHttpClientConfig {         @Bean     public OkHttpClient.Builder okHttpClientBuilder() {         return new OkHttpClient.Builder().addInterceptor(new FeignOkHttpClientResponseInterceptor());     }         /**      * okHttp响应拦截器      */     public static class FeignOkHttpClientResponseInterceptor implements Interceptor {             @Override         public Response intercept(Chain chain) throws IOException {               Request originalRequest = chain.request();             Response response = chain.proceed(originalRequest);               MediaType mediaType = response.body().contentType();             String content = response.body().string();                        //解析content,做你想做的事情!!!             System.out.print("拦截器"+content+"往哪跑?");               //生成新的response返回,网络请求的response如果取出之后,直接返回将会抛出异常             return response.newBuilder()                     .body(ResponseBody.create(mediaType, content))                     .build();         }     }       }  Feign使用okhttpclient时报错:java.lang.IllegalStateException: original request is required 问题复现:      2023-03-27 16:43:23.754 ERROR 21120 --- [ervice-client-1] c.l.c.s.f.TransactionFallBackFactory     : TransactionMsgWaitingConfirm Error : java.lang.IllegalStateException: original request is required     at feign.Util.checkState(Util.java:127)     at feign.Response.<init>(Response.java:48)     at feign.Response.<init>(Response.java:38)     at feign.Response$Builder.build(Response.java:133)     at feign.okhttp.OkHttpClient.toFeignResponse(OkHttpClient.java:99)     at feign.okhttp.OkHttpClient.execute(OkHttpClient.java:161) 具体描述:  可以看到状态200 请求成功 ,并且我设置的超时是5秒,而我查看后台日志请求只用了130ms说明没有超时,为什么一直走熔断? 为什么会一直报: java.lang.IllegalStateException: original request is required  解决方案:  最初我用的okhttp 版本是9.7.0 导致,feign-core 的版本和 feign-okhttp版本不一致问题引起,或者可以理解版本为不兼容导致,将 feign-okhttp版本换成 10.1.0 成方可解决。 
  • [其他] Spring Cloud 组件介绍和对比
    服务发现:Nacos、Eureka微服务架构里面的核心,负责微服务各个模块的服务注册和发现,各个微服务模块将自己的服务地址、端口号等元数据发送给注册中心,注册中心将此信息记录到服务列表中并通知发送给各个子模块中。 Nacos融合SpringCloud官方文档:cid:link_1Eurka Spring官方文档:cid:link_5远程服务调用(RPC):Dubbo、Feign协议Dubbo 支持多传输协议: Dubbo、Rmi、http,可灵活配置Feign 基于Http传输协议,短连接,性能比dubbo低与Spring Cloud 集成Dubbo 在早期Dubbo是与Spring Cloud 集成有一些脱落,但是在Spring Cloud Alibaba 出现后,spring-cloud-starter-dubbo 与Spring Cloud完美集成Feign Spring Cloud 最早支持的RPC框架,兼容性好文档Dubbo Apache文档:cid:link_7Dubbo融合Nacos教程:cid:link_2Feign:RestTemplate + Ribbon结合Eureka进行RPC调用。负载均衡:Ribbon、DubboDubboRibbonFeign自身是没有负载均衡能力的,之前默认使用Ribbon作为负载均衡的组件,但是Netfix 已经不在维护了 新版本的Spring Cloud已经将Ribbon替换成Spring Cloud Load Balancer,Ribbon是客户端级别的负载均衡,不像dubbo支持客户端和服务端双向配置网关:SpringCloud Gateway、Zuul、KongKong 网关:Kong 的性能非常好,非常适合做流量网关,但是对于复杂系统不建议业务网关用 Kong,主要是工程性方面的考虑。官网:cid:link_10Zuul1.x 网关:Zuul 1.0 的落地经验丰富,但是性能差、基于同步阻塞IO,适合中小架构,不适合并发流量高的场景,因为容易产生线程耗尽,导致请求被拒绝的情况gateway 网关:功能强大丰富,性能好,官方基准测试 RPS (每秒请求数)是Zuul的1.6倍,能与 SpringCloud 生态很好兼容,单从流式编程+支持异步上也足以让开发者选择它了。官网教程:cid:link_8Zuul 2.x:性能与 gateway 差不多,基于非阻塞的,支持长连接,但 SpringCloud 没有集成 zuul2 的计划,并且 Netflix 相关组件都宣布进入维护期,前景未知。教程:cid:link_4综上,gateway 网关更加适合 SpringCloud 项目,而从发展趋势上看,gateway 替代 zuul 也是必然的。配置中心:Consul、Nacos、ApolloConsul 官网:cid:link_9中文文档:cid:link_6Apollo 和 Nacos 提供了更多开箱即用的功能,更适合用来作为配置中心。 Nacos 使用起来比较简单,并且还可以直接用来做服务发现及管理。Apollo 只能用来做配置管理,使用相对复杂一些。 官网:cid:link_3Apollo 在配置管理方面做的更加全面,就比如说虽然 Nacos 在 1.1.0 版本开始支持灰度配置,但 Nacos 的灰度配置功能实现的比较简单,Apollo 实现的灰度配置功能就相对更完善一些。不过,Nacos 提供的配置中心功能已经可以满足绝大部分项目的需求了。官网:cid:link_0服务熔断:Hystrix、Sentinel什么是 Hystrix?在分布式环境中,不可避免地会遇到所依赖的服务挂掉的情况,Hystrix 可以通过增加 延迟容忍度 与 错误容忍度,来控制这些分布式系统的交互。Hystrix 在服务与服务之间建立了一个中间层,防止服务之间出现故障,并提供了失败时的 fallback 策略,来增加你系统的整体可靠性和弹性。Hystrix 做了那些事情?引入第三方的 client 类库,通过延迟与失败的检测,来保护服务与服务之间的调用(网络间调用最为典型)阻止复杂的分布式系统中出现级联故障快速失败与快速恢复机制提供兜底方案(fallback)并在适当的时机优雅降级提供实时监控、报警与操作控制Hystrix能干嘛?服务降级服务熔断服务限流接近实时的监控Sentinel 具有以下特征:丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。链路追踪:Spring Cloud Sleuth + zipkin微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去定位。主要体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以定位。所以微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题,很快定位。 举几个例子:在微服务系统中,一个来自用户的请求,请求先达到前端A(如前端界面),然后通过远程调用,达到系统的中间件B、C(如负载均衡、网关等),最后达到后端服务D、E,后端经过一系列的业务逻辑计算最后将数据返回给用户。对于这样一个请求,经历了这么多个服务,怎么样将它的请求过程的数据记录下来呢?这就需要用到服务链路追踪。分析微服务系统在大压力下的可用性和性能。 Zipkin可以结合压力测试工具一起使用,分析系统在大压力下的可用性和性能。Spring Cloud Sleuth 主要功能就是在分布式系统中提供追踪解决方案,并且兼容支持了 zipkin,你只需要在pom文件中引入相应的依赖即可。Sleuth与Zipkin关系spring cloud提供了spring-cloud-sleuth-zipkin来方便集成zipkin实现(指的是Zipkin Client,而不是Zipkin服务器),该jar包可以通过spring-cloud-starter-zipkin依赖来引入。ZipkinZipkin是什么 Zipkin分布式跟踪系统;它可以帮助收集时间数据,解决在microservice架构下的延迟问题;它管理这些数据的收集和查找;Zipkin的设计是基于谷歌的Google Dapper论文。 每个应用程序向Zipkin报告定时数据,Zipkin UI呈现了一个依赖图表来展示多少跟踪请求经过了每个应用程序;如果想解决延迟问题,可以过滤或者排序所有的跟踪请求,并且可以查看每个跟踪请求占总跟踪时间的百分比。为什么使用Zipkin 随着业务越来越复杂,系统也随之进行各种拆分,特别是随着微服务架构和容器技术的兴起,看似简单的一个应用,后台可能有几十个甚至几百个服务在支撑;一个前端的请求可能需要多次的服务调用最后才能完成;当请求变慢或者不可用时,我们无法得知是哪个后台服务引起的,这时就需要解决如何快速定位服务故障点,Zipkin分布式跟踪系统就能很好的解决这样的问题。Zipkin原理 针对服务化应用全链路追踪的问题,Google发表了Dapper论文,介绍了他们如何进行服务追踪分析。其基本思路是在服务调用的请求和响应中加入ID,标明上下游请求的关系。利用这些信息,可以可视化地分析服务调用链路和服务间的依赖关系。 对应Dpper的开源实现是Zipkin,支持多种语言包括JavaScript,Python,Java, Scala, Ruby, C#, Go等。其中Java由多种不同的库来支持 Spring Cloud Sleuth是对Zipkin的一个封装,对于Span、Trace等信息的生成、接入HTTP Request,以及向Zipkin Server发送采集信息等全部自动完成。Spring Cloud Sleuth的概念图见上图。
  • [技术干货] springcloud alibaba项目搭建踩坑记
    随着现在微服务架构的流行,学习微服务成为了大势所趋 。最新流行的应该就是springcloud Alibaba这一套技术栈了 。所以在网上搜索了资料进行了 整合学习 。下面分享一下 在项目搭建中遇到的一些坑 。背景:刚开始肯定是要参考大佬们的案例的 。于是在网上搜索了 大名鼎鼎的ry框架,cloud版本的。当然第一步就是先fork到自己的仓库 然后把代码下载下来了 。按照官方的指导,先在虚拟机上面安装了数据库,redis,nacos,之类相关的中间件。其中也遇到了很多坑,不过那些都不是重点,重点是当我启动完项目开始搭建自己的项目的时候坑来了 。1.版本间匹配关系。按照人家先有的springcloud springboot springcloud alibaba的版本 感觉好像什么都没问题一样 。然后我就开始都按照官网最新的版本来选。spring.io 进去搜索。结果项目导入进去各种报错,最基本的启动不起来,搜索后得知是有版本对应关系的。Spring Cloud Alibaba Version 2021.0.4.0*Spring Cloud Version Spring Cloud 2021.0.4Spring Boot Version 2.6.11Sentinel Version  1.8.5Nacos Version  2.0.4RocketMQ Version  4.9.4Seata Version 1.5.2这个是在官方的说明里面找到的版本对应说明。2.子父模块之间的maven依赖问题这个问题遇到的坑比较多,因为没自己搭建过,没能很好的梳理好子父间关系 。遇到如下问题Maven ‘parent.relativePath‘ of POM 发现是在《parent》 标签里面没有写<relativePath /> Non-resolvable parent POM for  遇到这个问题 是因为修改了父, 子项目之后没有重新构建父项目 导致包不对。最坑的就是这个问题了 SpringBoot工程启动不起来,报错Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.什么都没有动项目就没起来,问题在哪,我最后找了一圈发现由于pom文件对应的pom文件内置tomcat的写法上  问题出现前是这样的  <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-tomcat</artifactId>             <scope>provided</scope>         </dependency>  <dependency> https://bbs.huaweicloud.com/forum/thread-0221112501396435024-1-1.html  具体可以看我这篇帖子。暂时就遇到这么些问题,还没有搭建成功 还要继续努力。
  • [技术干货] 记微服务架构下服务互相调用踩的坑。
    最近单位的搞了个新的项目,项目架构从单体变成了微服务,之前对于微服务的一些知识都知识停留在纸面上,以及自己的一些demo上,没有真正的实操过 。这不,这下就踩了坑 。刚开始还不是很熟练,技术方面用到了 springcloud,nacos,feign,gateway。然后就在feign上踩坑了 。先简单的说下feign相关的注解@RequestLine//定义请求方式,请求路径,还有请求参数String value();// 是否编码/符号,默认是会编码的,也就是转义的意思boolean decodeSlash() default true;// 默认支持URL传多值,是通过key来传输的。形如:key=value1&key=value2&key=value3// CollectionFormat不同的取值对应不同的分隔符,一般不建议改CollectionFormat collectionFormat() default CollectionFormat.EXPLODED;@FeignClient标签的常用属性如下:name:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现url: url一般用于调试,可以手动指定@FeignClient调用的地址decode404:当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignExceptionconfiguration: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contractfallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码path: 定义当前FeignClient的统一前缀,当我们项目中配置了server.context-path,server.servlet-path时使用 。一般情况下在启动类里面,加入注解声明开启远程调用然后就可以了。我的需求是微服务中 服务A调用服务B,服务B里又调用服务C 。贴上我的代码 。然后这个时候看着只有2行代码但是就是调用不了,但是又不好意思问,因为只有2行都搞定不了实在是 。。就只能去搜索恶补知识。以为是哪里的其他配置细节没做好。@FeignClient(name="custorm",fallback=Hysitx.class) public interface IRemoteCallService {     @RequestMapping(value="/custorm/getTest",method = RequestMethod.POST)     List<String> test(@RequestParam("names") String[] names); 搜到了 如上所示代码 ,这个时候我就更坚定写的没问题了么。。在多次尝试未果之后问了同事才搞定 。、原来是@EnableFeignClients跨服务调用的注解。@EnableDiscoveryClient将一个微服务注册到Eureka Server上面的@FeignClient 这个注解是不行的 。  搜到的代码 和我的需求是不一样的 。真的是新手入门 ,一个小坑记录提醒自己 。
  • [技术干货] Feign整合Sentinel 开启 feign.sentinel.enabled=true 启动报错
     Feign 整合 Sentinel 开启 feign.sentinel.enabled=true 启动报错 Requested bean is currently in creation: Is there an unresolvable circular reference? Spring Boot 版本为:2.3.9.RELEASE Spring Cloud 版本为:Hoxton.SR10 Spring Cloud Alibaba 版本为:2.2.5.RELEASE  报错信息:   修改 Spring Cloud 版本为 Hoxton.SR8 启动成功  熔断回调写法: @FeignClient(value = "user-service",fallbackFactory = UserClientFallbackFactory.class) public interface UserClient {     @GetMapping("/user/getUserInfoById/{id}")     User getUserInfoById(@PathVariable Integer id);  ​启动项配置  熔断生效:    ———————————————— 原文作者:zhaozhangxiao 转自链接:https://learnku.com/articles/67789 版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请保留以上作者信息和原文链接。 
  • [技术干货] 应用服务版块问题解决汇总贴
    1.SpringCloud快速接入CSE启动报错:MicroserviceConfigLoader转String失败问题描述:在把springCloud快速接入CSE的时候,pom和配置修改完成,启动服务的时候报错,报错信息如下:org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.apache.servicecomb.config.archaius.sources.MicroserviceConfigLoader] to type [java.lang.String]请问这个是什么导致的?怎么解决?帖子链接:https://bbs.huaweicloud.com/forum/thread-194622-1-1.html2.CSE可以管理本地启动的微服务吗?问题描述:CSE可以管理本地启动的微服务吗?本地的服务是不是只需要配置华为云CSE上的注册服务发现地址和配置中心地址就可以管理微服务了?还是说必须要把服务部署后才能用CSE管理呢?帖子链接:https://bbs.huaweicloud.com/forum/thread-192451-1-1.html3.项目基于spring-cloud,注册中心用nacos,服务之间调用用的dubbo,现在项目迁移CSE,dubbo这块怎么配置?问题描述:现在公司项目基于spring-cloud-alibaba,注册中心用的nacos,但服务调用没有用openfeign,用的dubbo调的(也是注册到nacos,没有用zookeeper),如图:现在要迁移到华为云,使用CSE的注册中心和配置中心,这一块倒没有问题,官方有文档,就是不知道迁移过去dubbo这一块的配置应该怎么搞,官网只有个基于纯dubbo项目迁移CSE的方案(https://support.huaweicloud.com/devg-cse/cse_devg_0011.html),按照文档说明引入对应maven依赖,就直接报错:帖子链接:https://bbs.huaweicloud.com/forum/thread-192450-1-1.html4.ServiceComb打开Tls开关,如何能支持常规非CSE服务间访问的HTTP请求问题描述:ServiceComb打开Tls开关,如何能支持普通非CSE服务间访问的HTTP请求?打开sslEnabled之后,CSE服务间访问确实是走tls,但是常规http请求也是需要https,可否配置部分url走http的策略?帖子链接:https://bbs.huaweicloud.com/forum/thread-185501-1-1.html5.微服务引擎CSE中配置中心请问下怎么用,翻遍了文档没找到问题描述:在s3服务器上搭建了cse环境,下载了下载本地轻量化微服务引擎,搭建了注册中心配置中心,同时下载了demo 的basic中的三个服务注册到注册中心去在30103操作页面配置yaml时就遇到了问题,第一个没理解配置管理怎么用第二个,配置了配置项,但是返回读取的信息还是工程里的yaml配置的信息,并不是从配置中心获取的怎么样才能像spring-cloud-config那样让服务从配置中心读取配置项数据呢?哪位大佬帮忙解决下帖子链接:https://bbs.huaweicloud.com/forum/thread-185292-1-1.html
  • [干货汇总] 零代码修改,教你Spring Cloud应用轻松接入CSE
    【摘要】 Sermant Agent是一种基于JavaAgent的无代理服务网格技术。它利用JavaAgent来检测主机应用程序,并具有增强的服务治理功能,以解决海量微服务架构中的服务治理问题。本文介绍了Sermant Agent的接入原理和如何使用Sermant Agent无修改接入CSE。本文分享自华为云社区《Spring Cloud应用零代码修改接入华为云微服务引擎CSE》,作者: 微服务小助手 。一、 Sermant Agent介绍Sermant Agent是一种基于JavaAgent的无代理服务网格技术。它利用JavaAgent来检测主机应用程序,并具有增强的服务治理功能,以解决海量微服务架构中的服务治理问题。Sermant Agent处于快速发展阶段,当前已支持多种服务治理能力,包含流量治理、注册、优雅上下线及动态配置能力。 二、 为何使用Sermant Agent接入代码零侵入,配置很简单相较于SDK方式接入,基于Sermant Agent的接入会更加快捷高效,配置简单,且应用无需做任何代码改造,仅需在服务启动时附带Sermant Agent即可动态接入到CSE。支持多种治理能力Sermant Agent默认集成流量治理能力,当前支持熔断、限流、隔离仓以及重试治理能力,该能力可基于CSE配置中心进行配置与发布。支持多种注册中心Sermant Agent目前支持业内主流的注册中心,已经支持了ServiceComb ServiceCenter、Naocs,Eureka、Zookeeper等正在开发中。支持应用不停机迁移Sermant Agent支持服务的双注册,可根据配置中心下发的服务订阅策略,动态修改当前服务的订阅策略,并基于该能力帮助线上应用在业务不中断的前提下完成服务迁移。不仅如此,Sermant Agent提供优雅上下线能力,在服务重启、上下线时提供保障,在保护服务的同时,规避服务下线时可能存在的流量丢失问题。三、 接入原理当然,在说明原理之前,我们首先需要了解什么是Java Agent。Java Agent是在JDK1.5之后引入的新特性,它支持JVM将字节码文件读入内存之后,JVM使用对应的字节流在Java堆中生成一个Class对象之前,用户可以对其字节码进行修改的能力,JVM使用修改之后的字节码进行Class对象的创建,从而实现Java应用的非代码侵入的业务逻辑修改和替换。Sermant Agent正是基于动态修改字节码的技术,在服务启动时,动态增强原服务的注册逻辑。那Sermant Agent是如何在不修改代码的前提下接入CSE呢?主要流程如下:Sermant Agent接入CSE的时序图包含以下6个步骤:首先服务携带Sermant Agent启动;服务启动时,针对服务执行字节码增强操作(基于Java Agent的字节码增强),主要针对注册与配置两块,在步骤3-5体现;通过字节码增强,动态识别原应用的注册中心;注入启动配置,动态关闭原应用的注册中心自动配置逻辑;随后通过Spring的SpringFactory机制注入基于Spring Cloud实现的注册CSE的自动配置类,由Spring接管;当应用发起注册时,会通过步骤5注入的注册逻辑向CSE发起注册,最终完成接入。四、 简单零代码修改,轻松接入CSE接入场景分为虚机接入和容器接入,大家可以根据自身需求选择合适的接入方式。虚机场景接入CSE虚机部署的应用可通过Sermant Agent接入到CSE,点击查看虚机接入CSE流程。接入流程基于ECS将应用接入CSE流程如下:容器场景接入CSE容器部署的应用可通过Sermant Injector自动挂载Sermant Agent,从而通过Sermant Agent接入到CSE,点击查看容器接入CSE流程。接入流程基于CCE将应用接入CSE流程如下:五、 更多支持版本当前Sermant已支持大部分业内主流版本,相关Spring及注册中心版本如下:开源方式接入除了上述接入方式,还可基于开源方式接入,您可在Sermant开源社区拉取最新代码,并自行打包,启动步骤可参考虚机接入流程。开源项目Sermant:https://github.com/huaweicloud/Sermant
  • [问题求助] SpringCloud快速接入CSE启动报错:MicroserviceConfigLoader转String失败
    各位大佬,我在把springCloud快速接入CSE的时候,pom和配置修改完成,启动服务的时候报错,报错信息如下:org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.apache.servicecomb.config.archaius.sources.MicroserviceConfigLoader] to type [java.lang.String]请问这个是什么导致的?怎么解决?
  • [技术干货] springcloud详解
    Spring Cloud 是什么?在学习本课程之前,读者有必要先了解一下 Spring Cloud。Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性简化了分布式系统的开发,比如服务发现、服务网关、服务路由、链路追踪等。Spring Cloud 并不重复造轮子,而是将市面上开发得比较好的模块集成进去,进行封装,从而减少了各模块的开发成本。换句话说:Spring Cloud 提供了构建分布式系统所需的“全家桶”。Spring Cloud 现状目前,国内使用 Spring Cloud 技术的公司并不多见,不是因为 Spring Cloud 不好,主要原因有以下几点:Spring Cloud 中文文档较少,出现问题网上没有太多的解决方案。国内创业型公司技术老大大多是阿里系员工,而阿里系多采用 Dubbo 来构建微服务架构。大型公司基本都有自己的分布式解决方案,而中小型公司的架构很多用不上微服务,所以没有采用 Spring Cloud 的必要性。但是,微服务架构是一个趋势,而 Spring Cloud 是微服务解决方案的佼佼者,这也是作者写本系列课程的意义所在。Spring Cloud 优缺点其主要优点有:集大成者,Spring Cloud 包含了微服务架构的方方面面。约定优于配置,基于注解,没有配置文件。轻量级组件,Spring Cloud 整合的组件大多比较轻量级,且都是各自领域的佼佼者。开发简便,Spring Cloud 对各个组件进行了大量的封装,从而简化了开发。开发灵活,Spring Cloud 的组件都是解耦的,开发人员可以灵活按需选择组件。接下来,我们看下它的缺点:项目结构复杂,每一个组件或者每一个服务都需要创建一个项目。部署门槛高,项目部署需要配合 Docker 等容器技术进行集群部署,而要想深入了解 Docker,学习成本高。Spring Cloud 的优势是显而易见的。因此对于想研究微服务架构的同学来说,学习 Spring Cloud 是一个不错的选择。Spring Cloud 和 Dubbo 对比Dubbo 只是实现了服务治理,而 Spring Cloud 实现了微服务架构的方方面面,服务治理只是其中的一个方面。下面通过一张图对其进行比较:可以看出,Spring Cloud 比较全面,而 Dubbo 由于只实现了服务治理,需要集成其他模块,需要单独引入,增加了学习成本和集成成本。Spring Cloud 学习Spring Cloud 基于 Spring Boot,因此在研究 Spring Cloud 之前,本课程会首先介绍 Spring Boot 的用法,方便后续 Spring Cloud 的学习。本课程不会讲解 Spring MVC 的用法,因此学习本课程需要读者对 Spring 及 Spring MVC 有过研究。本课程共分为四个部分:第一部分初识 Spring Boot,掌握 Spring Boot 基础知识,为后续入门 Spring Cloud 打好基础 。第二部分 Spring Cloud 入门篇,主要介绍 Spring Cloud 常用模块,包括服务发现、服务注册、配置中心、链路追踪、异常处理等。第三部分 Spring Cloud 进阶篇,介绍大型分布式系统中事务处理、线程安全等问题,并以一个实例项目手把手教大家搭建完整的微服务系统。第四部分 Spring Cloud 高级篇,解析 Spring Cloud 源码,并讲解如何部署基于 Spring Cloud 的大型分布式系统。本课程的所有示例代码均可在:https://github.com/lynnlovemin/SpringCloudLesson 下载。
  • [技术干货] springcloud
    Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。外文名Spring Cloud性    质一系列框架的有序集合目录1 Spring Cloud组成2 Spring Cloud前景Spring Cloud组成编辑 播报Spring Cloud的子项目,大致可分成两类,一类是对现有成熟框架”Spring Boot化”的封装和抽象,也是数量最多的项目;第二类是开发了一部分分布式系统的基础设施的实现,如Spring Cloud Stream扮演的就是kafka, ActiveMQ这样的角色。对于我们想快速实践微服务的开发者来说,第一类子项目就已经足够使用,如:Spring Cloud Netflix  是对Netflix开发的一套分布式服务框架的封装,包括服务的发现和注册,负载均衡、断路器、REST客户端、请求路由等。Spring Cloud Config  将配置信息中央化保存, 配置Spring Cloud Bus可以实现动态修改配置文件Spring Cloud Stream  分布式消息队列,是对Kafka, MQ的封装Spring Cloud Security  对Spring Security的封装,并能配合Netflix使用Spring Cloud Zookeeper  对Zookeeper的封装,使之能配置其它Spring Cloud的子项目使用Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件中的一部分,它基于Netflix Eureka 做了二次封装,主要负责完成微服务架构中的服务治理功能。Spring Cloud前景编辑 播报Spring Cloud对于中小型互联网公司来说是一种福音,因为这类公司往往没有实力或者没有足够的资金投入去开发自己的分布式系统基础设施,使用Spring Cloud一站式解决方案能在从容应对业务发展的同时大大减少开发成本。同时,随着近几年微服务架构和Docker容器概念的火爆,也会让Spring Cloud在未来越来越“云”化的软件开发风格中立有一席之地,尤其是在五花八门的分布式解决方案中提供了标准化的、全站式的技术方案,意义可能会堪比当年Servlet规范的诞生,有效推进服务端软件系统技术水平的进步。
  • [整体安全] 【漏洞预警】Spring Cloud Function SPEL表达式注入漏洞
    漏洞描述:作为一种函数计算框架,Spring Cloud Function 是基于Spring Boot而产生的。Spring Cloud Function能抽象出所有传输细节和基础架构,允许开发人员保留所有熟悉的工具和流程,并专注于业务逻辑,提高业务效率。Spring Cloud Function 要实现高级别要求的目标:1.通过函数促进业务逻辑的实现。2.将业务逻辑的开发生命周期与特定运行时分离,以便相同的代码可以作为任务运行。3.支持Serverless 提供商之间的统一编程模型,以及独立运行的能力。4.在Serverless提供商上启用Spring Boot 功能。近期,Spring Cloud官方修复了一个Spring Cloud Function中的SPEL表达式注入漏洞。由于Spring Cloud Function中RoutingFunction类的apply方法将请求头中的“spring.cloud. function.routing-expression”参数作为Spel表达式进行处理,造成了Spel表达式注入漏洞。使得未经授权的远程攻击者可利用该漏洞执行任意代码。漏洞危害:攻击者利用此漏洞,可实现远程代码执行。通过发送恶意制作的请求,从而导致在主机上执行任意恶意代码。使得攻击者在用户运行应用程序时执行恶意程序,并控制这个受影响的系统。攻击者一旦访问该系统后,它会试图提升其权限,甚至控制企业的服务器。影响范围:3.0.0.RELEASE <= Spring Cloud Function<= 3.2.2注:发布时间为2019年11月22日至2022年2月17日漏洞等级:    高危修复方案:目前官方已针对此漏洞发布修复补丁,请受影响的用户尽快更新进行防护,官方链接:https://github.com/spring-cloud/spring-cloud-function/commit/0e89ee27b2e76138c16bcba6f4bca906c4f3744f注:目前官方暂未发布新版本,请持续关注更新链接:https://github.com/spring-cloud/spring-cloud-function/tags
  • [技术交流] SpringCloud项目接入华为云微服务引擎CSE
    ### 一、SpringCloud项目架构介绍 #### 1.项目地址: [https://gitee.com/caichunfu/dtse-practice-microservice](https://gitee.com/caichunfu/dtse-practice-microservice) #### 2.项目技术栈如下: 1. 注册中心:Eureka 2. 网关:zuul 3. 声明式调用:feign 4. 存储:OBS、RDS for MySql ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/18/1645173154462340346.png) #### 3.项目结构: 1. dtse-commons:项目的公共工具类 2. dtse-eureka-server:eureka注册中心集群 3. dtse-feign-client:feign调用客户端 4. dtse-obs-storage:服务端,完成对obs的操作,包括上传和删除obs对象 5. dtse-system:消费端,实现用户登录、查询用户头像、更新用户头像(调用服务端请求) 6. dtse-zuul-getway:项目网关 ### 二、SpringCloud Huawei改造方案 #### 1.替换技术栈对比 | SpringCloud | SpringCloud-Huawei | | ------------------ | ------------------------------------------------------------ | | Eureka注册中心 | [华为云CSE微服务引擎](https://support.huaweicloud.com/devg-cse/cse_devg_0010.html) | | zuul网关 | SpringCloud Huawei Getway | | SpringCloud config | [CSE Config](https://support.huaweicloud.com/devg-cse/cse_devg_0022.html) | #### 2.SpringCloud Huawei技术文档 [https://github.com/huaweicloud/spring-cloud-huawei](https://github.com/huaweicloud/spring-cloud-huawei) #### 3.SpringCloud Huawei基础Demo [https://github.com/huaweicloud/spring-cloud-huawei-samples/tree/master/basic](https://github.com/huaweicloud/spring-cloud-huawei-samples/tree/master/basic) #### 4.SpringCloud Huawei版本对照 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/18/1645174723327212698.png) #### 5.改造前设置 1. 下载本地CSE,下载地址:[https://support.huaweicloud.com/devg-cse/cse_devg_0036.html](https://support.huaweicloud.com/devg-cse/cse_devg_0036.html) 2. 将maven镜像改为华为镜像仓库: ``` <mirror> <id>huaweicloud</id> <mirrorOf>*</mirrorOf> <url>https://repo.huaweicloud.com/repository/maven/</url> </mirror> ``` 3. 项目版本选定 - SpringBoot:2.3.7.RELEASE - SpringCloud:Hoxton.SR9 - SpringCloud Huawei:1.8.0-Hoxton #### 6.改造方案 1. 移除微服务项目里所有的eureka依赖,删掉application.yml里的eureka的配置 ``` <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> ``` 2. 导入SpringCloud Huawei依赖 - 父工程里导入 ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> ``` - 子工程里导入 ```xml <dependency> <groupId>com.huaweicloud</groupId> <artifactId>spring-cloud-starter-huawei-service-engine</artifactId> </dependency> ``` 3. 添加bootstrap.yaml配置文件 ```yaml spring: application: name: #微服务名 cloud: servicecomb: discovery: enabled: true address: http://127.0.0.1:30100 appName: basic-application serviceName: ${spring.application.name} version: 0.0.1 healthCheckInterval: 30 config: serverAddr: http://127.0.0.1:30110 serverType: kie ``` 4. 解决启动中的报错 4.1. 需要编写IClientConfig配置类: ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/18/1645176166364496834.png) ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/18/1645176147975776732.png) 4.2. 配置类添加 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/18/1645176207306600820.png) ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/18/1645176216784964848.png) 4.3. 微服务里无数据库配置: ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/18/1645176276185922690.png) 需要在启动类上加`@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})` 4.4. feign调用报错 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/18/1645176345773477730.png) SpringCloud Huawei当前版本对负载均衡支持不够友好,openfeign自带负载均衡,建议使用RestTemplate *以下解决方法来自本站的帖子[SpringCloud项目接入华为云微服务引擎CSE(一)](https://bbs.huaweicloud.com/forum/thread-178657-1-1.html)* 4.5. 修改openfeign调用为RestTemplate - 在dtse-system--usercontroller--UpdateIconImage路径对应的方法里注释掉openfeign调用代码 - 添加RestTemplate方式调用的代码 ```java //请求url String url = "http://xxxx:xxxx/xx/x"; ​ //构造请求头 HttpHeaders httpHeaders = new HttpHeaders(); HttpHeaders headers = httpHeaders; headers.setContentType(MediaType.MULTIPART_FORM_DATA); ​ //FileSystemResource将文件变成流以发送 File file = MultipartFileToFile.multipartFileToFile(multipartFile); FileSystemResource fileSystemResource = new FileSystemResource(file); ​ //构造请求体,使用LinkedMultiValueMap MultiValueMap<String, Object> resultMap = new LinkedMultiValueMap<>(); resultMap.add("file", fileSystemResource); resultMap.add("obsParamsJson", obsParamsJson); ​ //HttpEntity封装整个请求报文 HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(resultMap, headers); ​ //postForObject发送请求体 String objURL = restTemplate.postForObject(url, httpEntity, String.class); ``` **MultipartFile转File:** ```java import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; ​ public class MultipartFileToFile { /** * MultipartFile 转 File * * @param file * @throws Exception */ public static File multipartFileToFile(MultipartFile file) throws Exception { File toFile = null; if (file.equals("") || file.getSize() <= 0) { file = null; } else { InputStream ins = null; ins = file.getInputStream(); toFile = new File(file.getOriginalFilename()); inputStreamToFile(ins, toFile); ins.close(); } return toFile; } //获取流文件 private static void inputStreamToFile(InputStream ins, File file) { try { OutputStream os = new FileOutputStream(file); int bytesRead = 0; byte[] buffer = new byte[8192]; while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) { os.write(buffer, 0, bytesRead); } os.close(); ins.close(); } catch (Exception e) { e.printStackTrace(); } } /** * 删除本地临时文件 * @param file */ public static void delteTempFile(File file) { if (file != null) { File del = new File(file.toURI()); del.delete(); } } } ``` 4.6 网关改造 - 删除原项目zuul的依赖,添加springcloudgateway的依赖和springcloudhuawei提供的网关依赖 ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.huaweicloud</groupId> <artifactId>spring-cloud-starter-huawei-service-engine-gateway</artifactId> </dependency> ``` - 添加网关配置文件(注意uri不要用lb地址,当前版本涉及负载均衡的均有问题,暂时用固定地址) ```yaml spring: main: allow-bean-definition-overriding: true cloud: gateway: # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。) routes: #路由标识(id:标识,具有唯一性) - id: dtse-system-route # 目标服务地址(uri:地址,请求转发后的地址) #uri: lb://dtse-system #不要用lb地址 uri: http://localhost:9090 filters: args: # 路由条件(predicates:断言,匹配 HTTP 请求内容) predicates: - Path=/** urifiler: login-uri: /login ``` - 定义全局过滤器实现鉴权 ```java @Component @Slf4j public class RouteConfiguration implements GlobalFilter, Ordered { ​ @Autowired JwtUtil jwtUtil; @Autowired URIFilter uriFilter; ​ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ​ ServerHttpRequest request = exchange.getRequest(); RequestPath path = request.getPath(); ​ // 2、登陆请求放行 if(path.value().contains(uriFilter.getLoginuri().get(0))){ System.out.println("登陆请求路经:request.getPath() = " + path.value()); log.info("登录"); return chain.filter(exchange); } //3、非登陆请求用户权限校验 String authorization = request.getHeaders().getFirst("Authorization"); if (!StringUtils.isEmpty((authorization))) { System.out.println("非登陆请求路径:request.getPath() = " + path.value()); //2、获取请求头中Token信息 String token = authorization.replace("Bearer", ""); ​ //3、Token校验 Claims claims = jwtUtil.parseToken(token) ; ​ //4、获取用户id,并将用户id传送到后端 if (claims == null) { try { throw new Exception(String.valueOf(ResultCode.UNAUTHENTICATED)); } catch (Exception e) { e.printStackTrace(); } return null; } String id = claims.getId(); ​ //5、添加用户请求头 request.mutate().header("userId",id).build(); return chain.filter(exchange); } ​ return chain.filter(exchange); } ​ @Override public int getOrder() { return 0; } } ``` - urifiler配置类 ```java @ConfigurationProperties(prefix = "urifiler", ignoreUnknownFields = false) @Data @Component public class URIFilter { private List<String> loginuri; } ``` 4.7 配置中心 在浏览器输入http://127.0.0.1:30103/,进入cse可视化界面,选择配置列表,创建配置项 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/21/1645427195901258209.png)
  • [技术交流] SpringCloud项目接入华为云微服务引擎CSE(二)
    目录1.    业务架构说明2.    开发环境3.    本地CSE开发环境搭建    3.1.    下载本地CSE    3.2.    解压,双击cse.exe    3.3.    登录CSE控制台4.    接入前准备    4.1.    下载实验工程源码,使用IDEA打开    4.2.    IDE 配置    4.3.    Maven配置5.    服务端CSE接入    5.1.    主pom修改    5.2.    dtse-feign-client接入    5.3.    dtse-obs-storage接入    5.4.    dtse_system接入    5.5.    dtse-zuuL-gateway网关接入6.    前端服务接入7.    其他接入中问题记录    1. 业务架构说明    项目Gitee地址     后端   https://gitee.com/caichunfu/dtse-practice-microservice     前端   https://gitee.com/HuaweiCloudDeveloper/dtse-practice-frontEnd/tree/devCSE改造前:    • 微服务包含4个微服务模块:zuul-gateway模块、Eureka注册中心、dtse-system模块、obs-storage模块;其中dtse-system模块、obs-storage模块是业务模块。    • 用户发送请求,微服务网关(zuul-gateway) 过滤器根据请求URI,路由和负载均衡请求到不同服务;同时利用JWT进行token校验请求的合法性。    • Eureka注册中心管理zuul-gateway、dtse-system、obs-storage微服务状态;    • dtse-system与obs-storage之间通过feign进行内部接口调用改造技术路径    • 引入使用spring-cloud-huawei    • 使用华为云CSE服务替换Eureka注册中心的功能    • 使用Spring Cloud Gateway替换zuul网关基线版本选择    查看 spring-cloud-huawei官网地址:    https://github.com/huaweicloud/spring-cloud-huawei     通过实践master分支与openFeign存在兼容问题,所以本次实践以Hoxton为基线版本,Hoxten与openFeign不存在兼容性问题    由于Spring Cloud Huawei与zuul调试中发现有兼容问题,所以将网关替换成Spring Cloud Gateway2. 开发环境JDKOpenjdk 1.8.0_312Maven3.6.3IDEA2021.2.2CSELocal-CSE-2.1.3-windows-amd64.zipspring-boot2.3.5.RELEASEspring-cloudHoxton.SR9spring-cloud-huawei1.8.0-Hoxton3. 本地CSE开发环境搭建3.1. 下载本地CSE    https://cse-bucket.obs.cn-north-1.myhuaweicloud.com/LocalCSE/Local-CSE-2.1.3-windows-amd64.zip 3.2. 解压,双击cse.exe3.3. 登录CSE控制台    http://127.0.0.1:30103 4. 接入前准备4.1. 下载实验工程源码,使用IDEA打开    源码地址 https://gitee.com/caichunfu/dtse-practice-microservice 4.2. IDE 配置4.3. Maven配置    使用华为云Maven5. 服务端CSE接入改造5.1. 主pom改造5.1.1. 主pom引入依赖5.1.2. 使用CSE做为注册中心,删除相关模块和依赖    a、删除eureka-server    b、引入华为spring cloud` <properties> <java.version>1.8</java.version> <spring-boot.version>2.3.5.RELEASE</spring-boot.version> <spring-cloud.version>Hoxton.SR9</spring-cloud.version> <spring-cloud-huawei.version>1.8.0-Hoxton</spring-cloud-huawei.version> </properties>`<!-- configure user spring cloud / spring boot versions --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- configure spring cloud huawei version --> <dependency> <groupId>com.huaweicloud</groupId> <artifactId>spring-cloud-huawei-bom</artifactId> <version>${spring-cloud-huawei.version}</version> <type>pom</type> <scope>import</scope> </dependency>5.2. dtse-feign-client接入5.2.1. 引入openfeign版本<dependency> <groupId>io.github.openfeign.form</groupId> <artifactId>feign-form-spring</artifactId> <version>3.8.0</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-openfeign-core</artifactId> <version>3.0.3</version> </dependency>5.3. dtse-obs-storage接入5.3.1. Pom文件处理5.3.1.1. 删除zuul依赖    删除spring-cloud-starter-netflix-zuul,增加spring-cloud-starter-netflix-ribbon <!--wmh add--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> <version>2.2.0.RELEASE</version> <scope>compile</scope> <exclusions> <exclusion> <artifactId>ribbon-transport</artifactId> <groupId>com.netflix.ribbon</groupId> </exclusion> <exclusion> <artifactId>rxnetty</artifactId> <groupId>io.reactivex</groupId> </exclusion> </exclusions> <optional>true</optional> </dependency> <!-- wmh delete <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> -->5.3.2. 引入CSE配置5.3.3. 处理com.netflix.client.config.IClientConfig‘ that could not be found问题    出现这种问题是因为启动类找不到(扫描)其他类的路径,处理方法有多种,我这边使用的是方法二    方法一:把启动类放在其他类文件包的同一级,而不要放在上一级    方法二:在启动类的标签中加入启动扫描的路径如下:    方法三: new个IClientConfig类,不过需要初始化,不然会出现空指针5.3.3.1. 方法二指定扫描路径:    SpringBootApplication指定扫描路径@SpringBootApplication(scanBasePackages = {"com.huaweicloud.controller","com.huaweicloud.service","com.huaweicloud.commons","com.huaweicloud.persistent"}) @EnableFeignClients @EnableDiscoveryClient public class OBSStorageMain { public static void main(String[] args) { SpringApplication.run(OBSStorageMain.class, args); } }5.3.3.2. 方法三增加config类:    IClientConfig 类,重点来了,就是这个类,如果不自己定义(openFeign 是可以自定义这个类的,然后自己初始化),那么就千万不要自己去创建一个 bean 出来,然后自己加上注解定义成配置类如下:@Configuration public class IClientConfig { @Bean public DefaultClientConfigImpl iClientConfig(){ return new DefaultClientConfigImpl(); } }    这玩意千万不要在程序里自己创建出来,可能很多初学者不是很懂,一开始有配置了这个,结果又只是单纯的 return 了一个没有任何属性的 DefaultClientConfigImpl 对象,然后 openFeign 就会使用你创建的这个对象,结果去初始化的时候,就会在源码里面报空指针异常,把这玩意去掉,基本就可以了,如果要自己定义,那记得把里面该有的属性都要初始化值。5.4. Dtse_system接入5.4.1. Pom文件处理    删除eureka client,引入华为service engine <!-- <dependency>--> <!-- <groupId>org.springframework.cloud</groupId>--> <!-- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>--> <!-- </dependency>--> <dependency> <groupId>com.huaweicloud</groupId> <artifactId>spring-cloud-starter-huawei-service-engine</artifactId> </dependency>删除netflix-hystrix <!-- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>-->5.4.2. 引入CSE配置5.4.3. 处理com.netflix.client.config.IClientConfig‘ that could not be found问题package com.huaweicloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication(scanBasePackages = {"com.huaweicloud.controller","com.huaweicloud.commons","com.huaweicloud.service","com.huaweicloud.persistent"}) @EnableFeignClients //开启feign客户端调用支持 public class SystemMain { public static void main(String[] args) { SpringApplication.run(SystemMain.class, args); } }5.5. Dtse-zuuL-gateway网关接入    使用spring cloud huawei与zuul有兼容性问题,所以切换到Spring Cloud Gateway5.5.1. Pom文件处理    spring-boot-starter-web排除spring-webmvc包,删除spring-cloud-starter-netflix-zuul包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </exclusion> </exclusions> </dependency>    删除eureka-client包 <!-- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>-->    引入spring-cloud-starter-gateway和huawei-service-engine-gateway <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.huaweicloud</groupId> <artifactId>spring-cloud-starter-huawei-service-engine-gateway</artifactId> </dependency>5.5.2. 引入CSE配置    修改网关配置5.5.3. 修改网关全局过滤器package com.huaweicloud.filter; import com.huaweicloud.commons.outhUtils.JwtUtil; import com.huaweicloud.commons.response.ResultCode; import com.huaweicloud.config.URIFilter; import io.jsonwebtoken.Claims; import org.apache.servicecomb.foundation.common.utils.JsonUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.server.RequestPath; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.lang.annotation.Annotation; @Component public class RouteConfiguration implements GlobalFilter, Ordered { @Autowired JwtUtil jwtUtil; @Autowired URIFilter uriFilter; private String writeJson(Object o) { try { return JsonUtils.writeValueAsString(o); } catch (Exception ee) { ee.printStackTrace(); } return null; } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("access filter......"); ServerHttpRequest request = exchange.getRequest(); RequestPath path = request.getPath(); System.out.println("收到请求路经:request.getPath() = " + path.value()); // 2、登陆请求放行 if(path.value().contains(uriFilter.getLoginuri().get(0))){ System.out.println("登陆请求路经:request.getPath() = " + path.value()); return chain.filter(exchange); } //3、非登陆请求用户权限校验 String authorization = request.getHeaders().getFirst("Authorization"); if (!StringUtils.isEmpty((authorization))) { System.out.println("非登陆请求路径:request.getPath() = " + path.value()); //2、获取请求头中Token信息 String token = authorization.replace("Bearer", ""); //3、Token校验 Claims claims = jwtUtil.parseToken(token) ; //4、获取用户id,并将用户id传送到后端 if (claims == null) { try { throw new Exception(String.valueOf(ResultCode.UNAUTHENTICATED)); } catch (Exception e) { e.printStackTrace(); } return null; } String id = claims.getId(); //5、添加用户请求头 request.mutate().header("userId",id).build(); return chain.filter(exchange); } return chain.filter(exchange); } @Override public int getOrder() { return 0; } }5.5.4. 处理com.netflix.client.config.IClientConfig‘ that could not be found问题package com.huaweicloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class},scanBasePackages = {"com.huaweicloud.config","com.huaweicloud.filter","com.huaweicloud.commons"}) @EnableDiscoveryClient public class SpringCloudGatewayMain { public static void main(String[] args) throws Exception { SpringApplication.run(SpringCloudGatewayMain.class, args); } }6. 前端服务接入    修改vue.config.js,配置服务网关服务的端口    修改login.Vue,通过网关经过systemmain统一接入,所以修改登录url    修改图片上传接口,和获取用户信息接口7. 其他接入中问题记录7.1. 方便openFeign调试,openFeign调试,增加Feign日志类config 增加类FeignConfigurationpackage com.huaweicloud.config; import feign.Logger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FeignConfiguration { /** * 日志级别 * * @return */ @Bean Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } }7.2. Idea 编译报错:Ambiguous method call. Both...    IDEA Settings... > Plugins > 搜索已安装的插件Hrisey Plugins > 禁用该插件 7.3. gateway报错org. springframework.cloud.gateway.config.GatewayAutoConfiguration required a bean of type 'org.springframework.http.codec.ServerCodec Configurer' that could not be found      spring-cloud-starter-gateway依赖与mvc是不兼容的,如果要引用spring-boot-starter-web需要把mvc排除 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </exclusion> </exclusions> </dependency>7.4. OpenFeign调用报错com.netflix.client.ClientException: Load balancer does not have available server for client: DTSE-OBS-STORAGE    yml 文件里面的服务名,要和 @FeignClient(value = "xxx") 里面的 xxx 一样,切记别弄错,大小写也要一致
  • [技术交流] SpringCloud项目接入华为云微服务引擎CSE(一)
    1.项目Gitee地址https://gitee.com/caichunfu/dtse-practice-microservice2.运行环境JDK1.8Maven3.6.3本地CSE引擎下载地址:https://support.huaweicloud.com/devg-cse/cse_devg_0036.html3.注意事项踩过的一些坑:3.1依赖导入报错需要把Maven的中央仓库地址改为华为中央仓库地址,修改setting.xml文件      <mirror>          <id>huaweicloud</id>          <mirrorOf>*,!HuaweiCloudSDK</mirrorOf>          <url>https://repo.huaweicloud.com/repository/maven/</url>     </mirror>3.2版本问题官网地址:https://github.com/huaweicloud/spring-cloud-huawei1)使用Hoxton分支,项目启动时ribbon的启动类会报错,建议使用master分支com.huaweicloud.servicecomb.discovery.ribbon.ServiceCombRibbonClientConfiguration required a bean of type 'com.netflix.client.config.IClientConfig' that could not be found.2)master分支1.8.0-2020.0.x与网关存在兼容性问题,建议使用1.6.1-2020.0.x版本3)master分支需要使用 springcloud 2020.x 的版本,这个版本移除了Netflix相关的组件,所以需要对组件进行替换3.3本次测试使用的版本   <properties>       <spring-boot.version>2.5.3</spring-boot.version>       <spring-cloud.version>2020.0.4</spring-cloud.version>       <spring-cloud-huawei.version>1.6.1-2020.0.x</spring-cloud-huawei.version>       <servicecomb.version>2.5.0</servicecomb.version>       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>   </properties>4.引入依赖4.1父工程引入 <dependencyManagement>      <!-- configure spring cloud huawei version -->      <dependency>        <groupId>com.huaweicloud</groupId>        <artifactId>spring-cloud-huawei-bom</artifactId>        <version>${spring-cloud-huawei.version}</version>        <type>pom</type>        <scope>import</scope>      </dependency>    </dependencies>  </dependencyManagement>4.2子工程引入,并删除eureka相关依赖<dependency>  <groupId>com.huaweicloud</groupId>  <artifactId>spring-cloud-starter-huawei-service-engine</artifactId></dependency>5.创建bootstrap.yml文件spring: application:   name: #微服务名 cloud:   servicecomb:     discovery:       enabled: true       address: http://127.0.0.1:30100       appName: #应用名       serviceName: ${spring.application.name}       version: 0.0.1       healthCheckInterval: 30     config:       serverAddr: http://127.0.0.1:30110       serverType: kie6.启动类添加注解@EnableDiscoveryClient启动本地微服引擎,访问http://127.0.0.1:30103/进入微服务引擎管理界面,查看服务是否注册成功7.启动前遇到的问题7.1报错信息:could not be registered. A bean with that name has already been defined in URL ​Action:​Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true​无法注册。URL中已经定义了一个具有该名称的bean​考虑重命名一个bean,或者通过设置Spring实现重写。主要的允许bean定义覆盖=true解决方案:yml配置文件添加配置spring: main:   allow-bean-definition-overriding: true7.2报错信息:Action:​Correct the classpath of your application so that it contains a single, compatible version of io.micrometer.core.instrument.distribution.DistributionStatisticConfig$Builder解决方案:pom.xml里面的依赖包有重复,需要将重复的依赖包删除7.3报错信息:此问题只会在使用Honxton版本时出现,建议使用master分支1.6.1-2020.0.x版本Parameter 0 of method ribbonServerList in com.huaweicloud.servicecomb.discovery.ribbon.ServiceCombRibbonClientConfiguration required a bean of type 'com.netflix.client.config.IClientConfig' that could not be found.​Action:​Consider defining a bean of type 'com.netflix.client.config.IClientConfig' in your configuration.解决方案:IClientConfig 类,这个类定义时,不能直接 return 一个没有任何属性的 DefaultClientConfigImpl 对象, openFeign 会在源码里面使用这个对象,报空指针异常,如果要自己定义,需要初始化里面该有的属性@Configurationpublic class IClientConfig {   @Bean   public DefaultClientConfigImpl iClientConfig(){           //网上很多的错误写法       //return new DefaultClientConfigImp();               //加上getClientConfigWithDefaultValues初始化参数       return DefaultClientConfigImpl.getClientConfigWithDefaultValues();   }}8.Feign远程调用使用SpringCloudHuawei做远程调用时会报错,可能兼容性存在问题为了验证问题还原了SpringCloud项目,openfeign调用不会报错报错信息:org.apache.servicecomb.service.center.client.exception.OperationException: get service instances list fails, statusCode = 400; message = Bad Request; content = {"errorCode":"400012","errorMessage":"Micro-service does not exist","detail":"Consumer[30b75156753bc55385a7ae74d0611c77fc5f7522][development/dtse-practice-microservice/dtse-system/0.0.1] find provider[development/dtse-practice-microservice/default/0+] failed, provider does not exist"}​Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: default解决---暂未解决:已对接后端技术专家,暂未解决,解决后会更新进度9.改为RestTemplate方式调用前端参数为MultipartFile和JSON,请求类型为POST    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)    public String upLoadOneFile(@RequestPart("file") MultipartFile file, @RequestParam("obsParamsJson") String obsParamsJson) throws IOException {                OBSStorageParams obsParams = JSON.parseObject(obsParamsJson, OBSStorageParams.class);​        String objURL = obsService.uploadOneFile(file.getInputStream(), obsParams);        return objURL;   }RestTemplate调用代码        //请求url        String url = "http://xxxx:xxxx/xx/x";​        //构造请求头        HttpHeaders httpHeaders = new HttpHeaders();        HttpHeaders headers = httpHeaders;        headers.setContentType(MediaType.MULTIPART_FORM_DATA);​        //FileSystemResource将文件变成流以发送        File file = MultipartFileToFile.multipartFileToFile(multipartFile);        FileSystemResource fileSystemResource = new FileSystemResource(file);​        //构造请求体,使用LinkedMultiValueMap        MultiValueMap<String, Object> resultMap = new LinkedMultiValueMap<>();        resultMap.add("file", fileSystemResource);        resultMap.add("obsParamsJson", obsParamsJson);​        //HttpEntity封装整个请求报文        HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(resultMap, headers);​        //postForObject发送请求体        String objURL = restTemplate.postForObject(url, httpEntity, String.class);MultipartFile转File import org.springframework.web.multipart.MultipartFile; import java.io.File;import java.io.FileOutputStream;import java.io.InputStream;import java.io.OutputStream;​public class MultipartFileToFile {    /**     * MultipartFile 转 File     *     * @param file     * @throws Exception     */    public static File multipartFileToFile(MultipartFile file) throws Exception {        File toFile = null;        if (file.equals("") || file.getSize() <= 0) {            file = null;       } else {            InputStream ins = null;            ins = file.getInputStream();            toFile = new File(file.getOriginalFilename());            inputStreamToFile(ins, toFile);            ins.close();       }        return toFile;   }    //获取流文件    private static void inputStreamToFile(InputStream ins, File file) {        try {            OutputStream os = new FileOutputStream(file);            int bytesRead = 0;            byte[] buffer = new byte[8192];            while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {                os.write(buffer, 0, bytesRead);           }            os.close();            ins.close();       } catch (Exception e) {            e.printStackTrace();       }   }    /**     * 删除本地临时文件     * @param file     */    public static void delteTempFile(File file) {    if (file != null) {        File del = new File(file.toURI());        del.delete();   }}}10.网关改造官网的说明:Spring Cloud Huawei Hoxton分支只提供Spring Cloud Gateway基于Ribbon的负载均衡,及其配套的基于流量治理和灰度发布功能。 Spring Cloud Huawei master(2020.0.x版本)分支只提供Spring Cloud Gateway基于Spring Cloud LoadBalance的负载均衡, 及其配套的基于流量治理和灰度发布功能。建议Spring Cloud Gateway升级到2020.0.x版本。由于原项目使用的网关为Zuul,需要改为Spring Cloud Gateway10.1删除原项目zuul的依赖,添加springcloudgateway的依赖和springcloudhuawei提供的网关依赖<dependency>   <groupId>org.springframework.cloud</groupId>   <artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency>   <groupId>com.huaweicloud</groupId>   <artifactId>spring-cloud-starter-huawei-service-engine-gateway</artifactId></dependency>10.2添加网关配置文件spring: main:   allow-bean-definition-overriding: true cloud:   gateway:    # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。)     routes:     #路由标识(id:标识,具有唯一性)       - id: dtse-system-route        # 目标服务地址(uri:地址,请求转发后的地址)         uri: lb://dtse-system         filters:             args: # 路由条件(predicates:断言,匹配 HTTP 请求内容)         predicates:           - Path=/**urifiler: login-uri: /login10.3定义全局过滤器实现鉴权package com.huaweicloud.filter;​import com.huaweicloud.commons.outhUtils.JwtUtil;import com.huaweicloud.commons.response.ResultCode;import com.huaweicloud.config.URIFilter;import io.jsonwebtoken.Claims;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.gateway.filter.GatewayFilterChain;import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.core.Ordered;import org.springframework.http.server.RequestPath;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.stereotype.Component;import org.springframework.util.StringUtils;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;​@Component@Slf4jpublic class RouteConfiguration implements GlobalFilter, Ordered {​    @Autowired    JwtUtil jwtUtil;    @Autowired    URIFilter uriFilter;​    @Override    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {​        ServerHttpRequest request = exchange.getRequest();        RequestPath path = request.getPath();​        // 2、登陆请求放行        if(path.value().contains(uriFilter.getLoginuri().get(0))){            System.out.println("登陆请求路经:request.getPath() = " + path.value());            log.info("登录");            return chain.filter(exchange);       }        //3、非登陆请求用户权限校验        String authorization = request.getHeaders().getFirst("Authorization");        if (!StringUtils.isEmpty((authorization))) {            System.out.println("非登陆请求路径:request.getPath() = " + path.value());            //2、获取请求头中Token信息            String token = authorization.replace("Bearer", "");​            //3、Token校验            Claims claims = jwtUtil.parseToken(token) ;​            //4、获取用户id,并将用户id传送到后端            if (claims == null) {                try {                    throw new Exception(String.valueOf(ResultCode.UNAUTHENTICATED));               } catch (Exception e) {                    e.printStackTrace();               }                return  null;           }            String id = claims.getId();​            //5、添加用户请求头            request.mutate().header("userId",id).build();            return chain.filter(exchange);       }​        return chain.filter(exchange);   }​    @Override    public int getOrder() {        return 0;   }}10.4urifiler配置类@ConfigurationProperties(prefix = "urifiler", ignoreUnknownFields = false)@Data@Componentpublic class URIFilter {   private List<String> loginuri;}11.配置中心的使用访问http://127.0.0.1:30103/进入微服务本地引擎管理界面选择配置列表,创建配置项
总条数:53 到第
上滑加载中