Ruoyi项目集成Feign, 用于远程调用
【代码】Ruoyi项目集成Feign, 用于远程调用。
·
1. 主函数中引入@EnableFeignClients, ruoyi版本3.8.6
@EnableFeignClients
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class RuoYiApplication {
public static void main(String[] args) {
// System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(RuoYiApplication.class, args);
}
}
2. 在业务模块里面引入openfeign
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
<!--管理springcloud版本,引入openfeign时不用添加version-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
3. 在父pom中引入springboot依赖配置
<dependencyManagement>
<dependencies>
<!-- SpringBoot的依赖配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
4. 在yml中添加feign的相关配置
# 是否开启hystrix。true:开启,false:不开启, 不开启的话feign调用失败不回走到回调函数里面
# 详细解释(当设置为 true 时,如果Feign客户端调用远程服务失败,Hystrix回退逻辑将被触发。如果为 false,Feign将简单地抛出异常,不会触发回退逻辑。)
feign:
hystrix:
enabled: true
rpc:
wbOauth2Service: https://api.weibo.com/oauth2
client:
config:
default:
logger-level: full
# hystrix超时时间, 不设置feign调用会很快超时,设置 Hystrix 命令的默认超时时间。如果远程服务调用的响应时间超过这个设置的值,Hystrix将认为这个命令失败,并触发回退逻辑
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000
# 请求参数
wb:
appKey: 66386414
appSecret: f4f9b07509d4cdab57c45917d92081d
5. 在业务模块中添加config文件, 放到相关配置文件
package com.ruoyi.business.config;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Configuration;
import javax.servlet.http.HttpServletRequest;
/**
* feign转发header参数
*/
@Configuration
public class FeignConfig implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
HttpServletRequest httpServletRequest = ServletUtils.getRequest();
if (StringUtils.isNotNull(httpServletRequest)) {
/*
可以做一些请求前的处理, 比如请求的接口需要在header中传token, 则可以再这个里面添加
Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
String authentication = headers.get(Constants.AUTHORIZATION_HEADER);
if (StringUtils.isNotEmpty(authentication)) {
requestTemplate.header(Constants.AUTHORIZATION_HEADER, authentication);
}*/
}
}
}
package com.ruoyi.business.config;
import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle;
import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;
import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;
import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
import com.netflix.hystrix.strategy.properties.HystrixProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 自定义Feign的隔离策略:
* 在转发Feign的请求头的时候, 如果开启了Hystrix,
* Hystrix的默认隔离策略是Thread(线程隔离策略), 因此转发拦截器内是无法获取到请求的请求头信息的,
* 可以修改默认隔离策略为信号量模式:hystrix.command.default.execution.isolation.strategy=SEMAPHORE,
* 这样的话转发线程和请求线程实际上是一个线程, 这并不是最好的解决方法, 信号量模式也不是官方最为推荐的隔离策略;
* 另一个解决方法就是自定义Hystrix的隔离策略:
* 思路是将现有的并发策略作为新并发策略的成员变量,在新并发策略中,
* 返回现有并发策略的线程池、Queue;将策略加到Spring容器即可;
*/
@Component
public class FeignHystrixConcurrencyStrategyIntellif extends HystrixConcurrencyStrategy {
Logger log = LoggerFactory.getLogger(this.getClass());
private HystrixConcurrencyStrategy delegate;
public FeignHystrixConcurrencyStrategyIntellif() {
try {
this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
if (this.delegate instanceof FeignHystrixConcurrencyStrategyIntellif) {
// Welcome to singleton hell...
return;
}
HystrixCommandExecutionHook commandExecutionHook =
HystrixPlugins.getInstance().getCommandExecutionHook();
HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
HystrixPropertiesStrategy propertiesStrategy =
HystrixPlugins.getInstance().getPropertiesStrategy();
this.logCurrentStateOfHystrixPlugins(eventNotifier, metricsPublisher, propertiesStrategy);
HystrixPlugins.reset();
HystrixPlugins instance = HystrixPlugins.getInstance();
instance.registerConcurrencyStrategy(this);
instance.registerCommandExecutionHook(commandExecutionHook);
instance.registerEventNotifier(eventNotifier);
instance.registerMetricsPublisher(metricsPublisher);
instance.registerPropertiesStrategy(propertiesStrategy);
} catch (Exception e) {
log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e);
}
}
private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier,
HystrixMetricsPublisher metricsPublisher,
HystrixPropertiesStrategy propertiesStrategy) {
if (log.isDebugEnabled()) {
log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy ["
+ this.delegate + "]," + "eventNotifier [" + eventNotifier + "]," + "metricPublisher ["
+ metricsPublisher + "]," + "propertiesStrategy [" + propertiesStrategy + "]," + "]");
log.debug("Registering Sleuth Hystrix Concurrency Strategy.");
}
}
@Override
public <T> Callable<T> wrapCallable(Callable<T> callable) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
return new WrappedCallable<>(callable, requestAttributes);
}
@Override
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
HystrixProperty<Integer> corePoolSize,
HystrixProperty<Integer> maximumPoolSize,
HystrixProperty<Integer> keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue) {
return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime,
unit, workQueue);
}
@Override
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
HystrixThreadPoolProperties threadPoolProperties) {
return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties);
}
@Override
public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
return this.delegate.getBlockingQueue(maxQueueSize);
}
@Override
public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
return this.delegate.getRequestVariable(rv);
}
static class WrappedCallable<T> implements Callable<T> {
private final Callable<T> target;
private final RequestAttributes requestAttributes;
public WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) {
this.target = target;
this.requestAttributes = requestAttributes;
}
@Override
public T call() throws Exception {
try {
RequestContextHolder.setRequestAttributes(requestAttributes);
return target.call();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
}
}
package com.ruoyi.business.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@Data
@ConfigurationProperties(prefix = "wb")
public class WbConfig {
private String appKey;
private String appSecret;
}
6. 新建factory和service文件夹, 放请求接口
package com.ruoyi.business.factory;
import com.ruoyi.business.domain.dto.WxOAuth2RequestDTO;
import com.ruoyi.business.rpc.RemoteWbOauth2Rpc;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class RemoteEEapiRpcFallbackFactory implements FallbackFactory<RemoteWbOauth2Rpc> {
@Override
public RemoteWbOauth2Rpc create(Throwable throwable) {
// log.error("调用微博oauth2接口出错:", throwable); // 使用 ":" 来打印完整的错误堆栈
if (throwable instanceof FeignException) {
FeignException feignException = (FeignException) throwable;
log.error("调用微博oauth2接口出错:状态码 {}, 请求详情: {}, 响应体: {}",
feignException.status(),
feignException.request(),
feignException.contentUTF8());
} else {
log.error("调用微博oauth2接口出错:{}", throwable.getMessage(), throwable);
}
return new RemoteWbOauth2Rpc() {
@Override
public String authorize(String clientId, String redirectUri, String state) {
return "调用请求用户授权code接口出错:" + throwable.getMessage();
}
@Override
public String accessToken(String appKey, String appSecret, String authorizationCode, String redirectUri, String code) {
return "调用获取access_token接口出错:" + throwable.getMessage();
}
@Override
public String accessToken2(WxOAuth2RequestDTO dto) {
return "调用获取access_token接口出错:" + throwable.getMessage();
}
};
}
}
package com.ruoyi.business.rpc;
import com.ruoyi.business.config.FeignConfig;
import com.ruoyi.business.factory.RemoteEEapiRpcFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "wbOauth2Service", url = "${feign.rpc.wbOauth2Service}", configuration = FeignConfig.class, fallbackFactory = RemoteEEapiRpcFallbackFactory.class)
public interface RemoteWbOauth2Rpc {
@GetMapping(value = "/authorize?client_id={clientId}&redirect_uri={redirectUri}&response_type=code&state={state}", consumes = MediaType.APPLICATION_JSON_VALUE)
String authorize(@PathVariable("clientId") String clientId, @PathVariable("redirectUri") String redirectUri,@PathVariable("state") String state);
@PostMapping(value = "/access_token", consumes = MediaType.APPLICATION_JSON_VALUE)
String accessToken(@RequestParam("client_id") String appKey, @RequestParam("client_secret") String appSecret,@RequestParam("grant_type") String authorizationCode, @RequestParam("redirect_uri") String redirectUri,@RequestParam("code") String code);
@PostMapping(value = "/access_token",consumes = MediaType.APPLICATION_JSON_VALUE)
String accessToken2(@RequestBody WxOAuth2RequestDTO dto);
}
7. 实际service调用, 解析返回值的两种方式
package com.ruoyi.business.service;
import com.google.gson.Gson;
import com.ruoyi.business.config.IpConfig;
import com.ruoyi.business.config.WbConfig;
import com.ruoyi.business.domain.dto.WbOAuth2UserInfoDTO;
import com.ruoyi.business.domain.vo.IpVO;
import com.ruoyi.business.rpc.RemoteIpRpc;
import com.ruoyi.business.rpc.RemoteWbOauth2Rpc;
import com.ruoyi.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
@Slf4j
public class FeignService {
@Resource
private RemoteWbOauth2Rpc remoteWbOauth2Rpc;
public WbOAuth2UserInfoDTO getAccessTokenWb(String code, String redirectUri) {
if (StringUtils.isEmpty(code)) {
throw new RuntimeException("code不能为空");
}
if (StringUtils.isEmpty(redirectUri)) {
throw new RuntimeException("redirectUri不能为空");
}
String responseStr = remoteWbOauth2Rpc.accessToken(wbConfig.getAppKey(), wbConfig.getAppSecret(), "authorization_code", redirectUri, code);
Gson gson = new Gson();
// 将JSON字符串转换为response
WbOAuth2UserInfoDTO response = null;
try {
response = gson.fromJson(responseStr, WbOAuth2UserInfoDTO.class);
} catch (Exception e) {
log.error("获取access_token后用户信息response:{}", responseStr);
throw new RuntimeException("授权失败, 请稍后尝试");
}
log.info("获取access_token后用户信息response:{}", response);
return response;
}
public String getAuthUrlWb(String url, String state) {
return remoteWbOauth2Rpc.authorize(wbConfig.getAppKey(), url, state);
}
}
8. 有时候springboot版本问题需要调整下跨越
/**
* 跨域配置
*/
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 设置访问源地址
// config.addAllowedOriginPattern("*");
config.addAllowedOrigin("*");
// 设置访问源请求头
config.addAllowedHeader("*");
// 设置访问源请求方法
config.addAllowedMethod("*");
// 有效期 1800秒
config.setMaxAge(1800L);
// 添加映射路径,拦截一切请求
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
// 返回新的CorsFilter
return new CorsFilter(source);
}
更多推荐
已为社区贡献1条内容
所有评论(0)