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);
    }
    
Logo

快速构建 Web 应用程序

更多推荐