<properties>
<spring-boot.version>2.5.6spring-boot.version>
<spring-cloud.version>2020.0.4spring-cloud.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>${spring-boot.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
#是否使用okhttp 需要加feign.okhttp jar包
feign.okhttp.enabled=true
#是否使用http 需要加feign.httpclient jar包
feign.httpclient.enabled=false
#feign.httpclient连接池最大连接数,默认200
feign.httpclient.max-connections=500
#feign.httpclient连接超时时间 默认2s
feign.httpclient.connection-timeout=10000
feign.client默认连接超时时间 5s
feign.client.config.default.connect-timeout=5000
#feign.client读超时时间 30s
feign.client.config.default.read-timeout=180000
#feign.client日志等级
feign.client.config.default.logger-level=full
#启用熔断机制
feign.circuitbreaker.enabled=true
##########################使用resilience4j做断路器 配置开始##########################
# 是否向 Actuator 的 HealthIndicator 注册
resilience4j.circuitbreaker.configs.default.registerHealthIndicator=true
#失败请求百分比,超过这个比例,CircuitBreaker就会变成OPEN状态 默认50
resilience4j.circuitbreaker.configs.default.failureRateThreshold=30
#慢调用时间,当一个调用慢于这个时间时,会被记录为慢调用 默认60000[ms]
resilience4j.circuitbreaker.configs.default.slowCallDurationThreshold=60000
#当慢调用达到这个百分比的时候,CircuitBreaker就会变成OPEN状态 默认100
resilience4j.circuitbreaker.configs.default.slowCallRateThreshold=100
#当CircuitBreaker处于HALF_OPEN状态的时候,允许通过的请求数量 默认10
resilience4j.circuitbreaker.configs.default.permittedNumberOfCallsInHalfOpenState=5
#滑动窗口类型,COUNT_BASED代表是基于计数的滑动窗口,TIME_BASED代表是基于计时的滑动窗口 默认COUNT_BASED
resilience4j.circuitbreaker.configs.default.slidingWindowType=TIME_BASED
#滑动窗口大小,如果配置COUNT_BASED默认值100就代表是最近100个请求,如果配置TIME_BASED默认值100就代表是最近100s的请求 默认100
resilience4j.circuitbreaker.configs.default.slidingWindowSize=10
#最小请求个数。只有在滑动窗口内,请求个数达到这个个数,才会触发CircuitBreaker对于是否打开断路器的判断 默认100
resilience4j.circuitbreaker.configs.default.minimumNumberOfCalls=5
#从OPEN状态变成HALF_OPEN状态需要的等待时间 默认60000[ms]
resilience4j.circuitbreaker.configs.default.waitDurationInOpenState=2s
#如果设置为true代表是否自动从OPEN状态变成HALF_OPEN,即使没有请求过来 默认false
resilience4j.circuitbreaker.configs.default.automaticTransitionFromOpenToHalfOpenEnabled=true
#异常名单,指定一个 Exception 的 list,所有这个集合中的异常或者这些异常的子类,在调用的时候被抛出,
#都会被记录为失败。其他异常不会被认为是失败,或者在 ignoreExceptions 中配置的异常也不会被认为是失败。 默认值empty
resilience4j.circuitbreaker.configs.default.recordExceptions[0]=java.lang.Exception
#异常白名单,在这个名单中的所有异常及其子类,都不会认为是请求失败,就算在 recordExceptions 中配置了这些异常也没用。默认empty
#resilience4j.circuitbreaker.configs.default.ignoreExceptions[0]=
#最大线程池大小 默认Runtime.getRuntime().availableProcessors()
resilience4j.thread-pool-bulkhead.configs.default.maxThreadPoolSize=50
#最核心线程池大小 默认Runtime.getRuntime().availableProcessors()-1
resilience4j.thread-pool-bulkhead.configs.default.coreThreadPoolSize=10
#队列大小 默认100
resilience4j.thread-pool-bulkhead.configs.default.queueCapacity=10
##########################使用resilience4j做断路器 配置结束##########################
<parent>
<groupId>com.projectgroupId>
<artifactId>parent-projectartifactId>
<version>1.0version>
parent>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-netflix-ribbonartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>io.github.openfeign.formgroupId>
<artifactId>feign-form-springartifactId>
dependency>
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-okhttpartifactId>
dependency>
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-httpclientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-circuitbreaker-resilience4jartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-circuitbreaker-spring-retryartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4jartifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-spring-cloud2artifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-feignartifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-circuitbreakerartifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-ratelimiterartifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-bulkheadartifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-retryartifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-cacheartifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-timelimiterartifactId>
dependency>
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class SysProjectApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(SysProjectApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
// 注意这里要指向原先用main方法执行的Application启动类
return builder.sources(SysProjectApplication.class);
}
}
@Api(value="角色管理接口集",tags={"角色管理接口集"})
@RestController
@RequestMapping("/_api/role")
public class _RoleController {
@Autowired
private IRoleService roleService;
@ApiOperation(value="查询拥有指定资源的用户", httpMethod="GET")
@ApiImplicitParam(name="resourceCode", value="指定的资源编号", required=true)
@RequestMapping(value="/find/by_rsCode", method= RequestMethod.GET)
public ApiResultDTO<List<SysDataSimpleDTO>> findUserByRsCode(@RequestParam String resourceCode, HttpServletRequest hreq) {
return RestAPITemplate.restapi(new IMyLogic<List<SysDataSimpleDTO>>() {
@Override
public List<SysDataSimpleDTO> logic() {
AccessTokenUser user=new AccessTokenUserAssembler().getAccessTokenUserFromReq(hreq);
return roleService.findUserByRsCode(user,resourceCode);
}
});
}
}
<parent>
<groupId>com.xysdgroupId>
<artifactId>xysd-parentartifactId>
<version>1.0version>
parent>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-netflix-ribbonartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>io.github.openfeign.formgroupId>
<artifactId>feign-form-springartifactId>
dependency>
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-okhttpartifactId>
dependency>
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-httpclientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-circuitbreaker-resilience4jartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-circuitbreaker-spring-retryartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4jartifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-spring-cloud2artifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-feignartifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-circuitbreakerartifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-ratelimiterartifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-bulkheadartifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-retryartifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-cacheartifactId>
dependency>
<dependency>
<groupId>io.github.resilience4jgroupId>
<artifactId>resilience4j-timelimiterartifactId>
dependency>
@EnableFeignClients
@EnableDiscoveryClient
@EnableTransactionManagement
@SpringBootApplication
public class XfxtProjectApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(XfxtProjectApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
// 注意这里要指向原先用main方法执行的Application启动类
return builder.sources(XfxtProjectApplication.class);
}
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
//允许上传的文件最大值
factory.setMaxFileSize(DataSize.parse("50MB")); //KB,MB
/// 设置总上传数据总大小
factory.setMaxRequestSize(DataSize.parse("50MB"));
return factory.createMultipartConfig();
}
}
@FeignClient(
name="sys-project",
contextId="ISysProjectGatewayServiceFeign",
path="/sys-project/_api",
configuration= FeignClientConfig.class
)
public interface ISysProjectGatewayService {
/**
* 查询拥有指定资源的用户
* @param resourceCode
* @return
*/
@GetMapping("/role/find/by_rsCode")
ApiResultDTO<List<SysDataSimpleDTO>> findUserByRsCode(@RequestParam("resourceCode") String resourceCode);
}
@Configuration
@AutoConfigureBefore({FeignClientsConfiguration.class})
public class FeignClientConfig{
@Autowired
private IBeansFactoryService beansFactoryService;
//解决 post 的url编码,和mutipart/from-data文件上传问题
@Bean
@Primary
public Encoder multipartFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder(Encoder encoder) {
return Feign.builder()
.encoder(encoder)//编码
;
}
//增加请求头
@Bean
public RequestInterceptor headerInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
String url=template.feignTarget().url();
if(StringUtils.isBlank(url)) return;
//logger.error("--------------------------FeignClient["+url+"]开始--------------------------");
AccessTokenUser user = null;
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String access_token=null;
if(attributes!=null) {
HttpServletRequest hreq = attributes.getRequest();
user=new AccessTokenUserAssembler().getAccessTokenUserFromReq(hreq);
}
if(user!=null) access_token=user.getAccessToken();
if(StringUtils.isNotBlank(access_token)) {
template.header("access_token", access_token);
}
//api接口
boolean api=url.indexOf("/api")>0;
if(!api) api=template.url().startsWith("/api/")||template.url().startsWith("api/");
if(api) {
//logger.error("--------------------------FeignClient["+url+"]access_token["+access_token+"]--------------------------");
return;
}
//已设置过访问令牌 以访问令牌为准 返回
if(StringUtils.isNotBlank(access_token)) {
//logger.error("--------------------------FeignClient["+url+"]access_token["+access_token+"]--------------------------");
return;
}
//_api接口
boolean _api=url.indexOf("/_api")>0;
if(!_api) api=template.url().startsWith("/_api/")||template.url().startsWith("_api/");
if(!_api) return;//非_api接口 返回 不处理
if(user==null) user=AccessTokenUser.createSystemUser();//不存在 令牌用户 创建系统用户
//为_api接口 创建 内部接口访问令牌
AccessTokenUser _user=user;
//BUG:为了避免循环依赖不允许通过autowired注入,这里直接通过静态方法获取ICachedTokenService
ICachedTokenService tokenService = beansFactoryService.getBeanOfType(ICachedTokenService.class);
if(tokenService!=null) {
String innerToken=ThreadLocalCache.fetchAPIData(null,()->{
return tokenService.createInnerToken(_user);
},"生成内部访问令牌错误,");
template.header("inner_token", innerToken);
}
}
};
}
}
Spring Cloud OpenFeign是一个声明式的REST客户端,使得编写Web服务客户端更加简单。它基于Feign,一个Spring Cloud组件,是一个轻量级的RESTful HTTP客户端。OpenFeign支持可插拔的编码器和解码器,并集成了Ribbon,用于客户端负载均衡,可以调用服务注册中心的服务。OpenFeign还支持Spring MVC注解,如@RequestMapping等,并利用Feign的高扩展性,使用标准Spring Web MVC来声明客户端Java接口。使用OpenFeign时,只需定义服务接口,然后在上面添加注解。
注意: 高版本OpenFeign底层不使用Ribbon做负载均衡。(SpringCloud 2020.0.x版本开始之后的版本)
Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-feignartifactId>
dependency>
OpenFeign是Spring Cloud 在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
Feign是在2019就已经不再更新了,通过maven网站就可以看出来,随之取代的是OpenFeign,从名字上就可以知道,他是Feign的升级版。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FeignClient {
/**
*作用同name属性 服务的名称(被调用的服务)
*/
@AliasFor("name")
String value() default "";
/**
* 如果指定这个属性,则bean的名称则取这个值,否则取name属性值
*/
String contextId() default "";
/**
*作用同value属性
*/
@AliasFor("value")
String name() default "";
/**
* 给当前bean指定一个名称
*/
@Deprecated
String qualifier() default "";
/**
* 给当前bean指定多个名称
*/
String[] qualifiers() default {};
/**
*指定被调用服务的url 绝对路径 一般用于调试
*/
String url() default "";
/**
* 是否启用404解码。默认为 false。
* 如果设置为 true,当请求返回404时,会抛出异常而不是返回null。
*/
boolean decode404() default false;
/**
*指定自定义配置文件
*/
Class<?>[] configuration() default {};
/**
* 指定一个 fallback bean,当远程调用失败时,会调用这个 bean。这通常用于实现断路器、重试等高级特性。
* 指定的bean 必须要要实现当前接口
*/
Class<?> fallback() default void.class;
/**
*指定一个 fallback factory bean,用于创建 fallback 实例。这通常与 fallback 属性一起使用,用于提供更复杂的 fallback 逻辑。
*/
Class<?> fallbackFactory() default void.class;
/**
* 指定被调用服务返回地址前缀
*/
String path() default "";
/**
* 存在多个相同类型的服务时,指定主要第一使用的服务
*/
boolean primary() default true;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
/**
* 作用同basePackages 指定包扫描路径
*/
String[] value() default {};
/**
* 指定包扫描路径
*/
String[] basePackages() default {};
/**
* 指定要扫描的那些类
*/
Class<?>[] basePackageClasses() default {};
/**
*自定义Feign配置文件
*/
Class<?>[] defaultConfiguration() default {};
/**
* 指定有@FeignClient注解的类
*/
Class<?>[] clients() default {};
}
@AutoConfigureBefore 是 Spring Boot 的一个注解,用于指定一个配置类应该在其他配置类之前执行。这个注解可以用于自定义自动配置类,以便在 Spring Boot 自动配置之前执行一些自定义的配置。
//依赖引入CircuitBreaker 所以这里的配置类使用这个
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CircuitBreaker.class)
@ConditionalOnProperty("feign.circuitbreaker.enabled")
protected static class CircuitBreakerPresentFeignBuilderConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean({ Feign.Builder.class, CircuitBreakerFactory.class })
public Feign.Builder defaultFeignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnBean(CircuitBreakerFactory.class)
public Feign.Builder circuitBreakerFeignBuilder() {
return FeignCircuitBreaker.builder();
}
}
public class ReflectiveFeign extends Feign {
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
}