SpringCloud实用-OpenFeign 调用三方接口

news2024/11/24 16:53:00

文章目录

  • 前言
  • 正文
    • 一、项目环境
    • 二、项目结构
      • 2.1 包的含义
      • 2.2 代理的场景
    • 三、完整代码示例
      • 3.1 定义FeignClient
      • 3.2 定义拦截器
      • 3.3 配置类
      • 3.4 okhttp配置
      • 3.5 响应体
        • 3.5.1 天行基础响应
        • 3.5.2 热点新闻响应
      • 3.6 代理类
        • 3.6.1 代理工厂
        • 3.6.2 代理客户端
        • 3.6.3 FeignClient的建造器
    • 四、调用&测试
      • 4.1 配置信息
      • 4.2 测试代码
      • 4.3 调用结果
  • 附录

前言

一般来说我们的项目中难免会涉及到调用三方接口的场景。
以前我们可能用 RestTemplate,或者再用OkHttp优化一下。

但是,在读了本文之后,你将发现使用OpenFeign的 FeignClient来调用三方接口,也是纵享丝滑的。

注意,本文旨在使用FeignClient调用三方接口,不讨论其他情况。比如高版本JDK自带的工具类,或者响应式API。

本文使用FeignClient来调用天行API接口。(https://www.tianapi.com/)
在天行官网注册账号后,可以申请自己想要调用的API接口。拿到key之后即可调用。每天都有免费的使用次数。

正文

一、项目环境

项目使用 Java 17、SpringBoot 3.3.4 、SpringCloud 2023.0.3

<properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <spring-boot.version>3.3.4</spring-boot.version>
        <spring-cloud.version>2023.0.3</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.32</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.32</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-okhttp -->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-loadbalancer -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
    </dependencies>


<dependencyManagement>
      <dependencies>
      <!-- springboot依赖 -->
          <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>
   </dependencies>
 </dependencyManagement>

二、项目结构

在这里插入图片描述

2.1 包的含义

  • com.pine.client.core.tianxing: 定义FeignClient接口;
  • com.pine.client.interceptor:拦截器
  • com.pine.client.config:配置类
  • com.pine.client.proxy:代理相关内容
  • com.pine.client.beans:请求体+响应体

2.2 代理的场景

在这里插入图片描述
假设你现在有内网和外网两种网络环境,应用部署在内网,现在你的应用需要访问外部三方接口,需要开白名单;

但是,一般而言,不会直接给你的应用开启白名单,会统一经过一个代理机进行跳转,也就是给内网中的代理机开启白名单,而你的应用使用它作为代理去访问三方接口。

三、完整代码示例

我这里接入三方接口的是:https://www.tianapi.com/apiview/68

温馨提示:天行API的接口文档不可尽信,有的响应体结构对应不上,建议在开发时,可以先用JsonNode接收,然后看实际的响应结构是什么,再定义对象去接收。

3.1 定义FeignClient

package com.pine.client.core.tianxing;

import com.pine.client.beans.TianXingNetHotResponse;
import com.pine.client.config.TianXingRequestConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;

/**
 * 天行feign client
 *
 * @author pine manage
 * @since 2024-11-22
 */
@FeignClient(name = "tianXing", url = "${rpc.tianxing.url}", configuration = TianXingRequestConfiguration.class)
public interface TianXingFeignClient {

    @PostMapping(value = "/nethot/index")
    TianXingNetHotResponse netHot();
}

3.2 定义拦截器

package com.pine.client.interceptor;

import feign.RequestInterceptor;
import feign.RequestTemplate;

/**
 * 天行 feign请求拦截器
 *
 * @author pine manage
 * @since 2024-11-22
 */
public class TianXingRequestInterceptor implements RequestInterceptor {

    private final String key;

    public TianXingRequestInterceptor(String key) {
        this.key = key;
    }

    @Override
    public void apply(RequestTemplate requestTemplate) {
        // 请求头增加参数 content-type
        requestTemplate.header("Content-Type", "application/x-www-form-urlencoded");
        // 请求参数增加key
        requestTemplate.query("key", key);
    }
}

3.3 配置类

package com.pine.client.config;

import com.pine.client.interceptor.TianXingRequestInterceptor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 天行接口请求配置
 *
 * @author pine manage
 * @since 2024-11-22
 */
@Configuration
public class TianXingRequestConfiguration {

    @Value("${rpc.tianxing.key}")
    private String tianXingKey;

    @Bean
    public TianXingRequestInterceptor tianXingRequestInterceptor() {
        return new TianXingRequestInterceptor(tianXingKey);
    }
}

3.4 okhttp配置

package com.pine.client.config;

import lombok.extern.slf4j.Slf4j;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;

import java.io.IOException;

/**
 * okhttp配置
 *
 * @author pine manage
 * @since 2024-11-22
 */
@Slf4j
@Configuration
public class OkHttpConfig {

    @Bean
    public okhttp3.OkHttpClient.Builder okHttpClientBuilder() {
        return new okhttp3.OkHttpClient.Builder()
                .addInterceptor(new LoggingInterceptor());
    }

    /**
     * okhttp3 请求日志拦截器
     */
    static class LoggingInterceptor implements Interceptor {
        
        @NonNull
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            long start = System.nanoTime();
            log.info(String.format("Sending request %s on %s%n%s",
                    request.url(), chain.connection(), request.headers()));
            Response response = chain.proceed(request);
            long end = System.nanoTime();
            log.info(String.format("Received response for %s in %.1fms%n%s",
                    response.request().url(), (end - start) / 1e6d, response.headers()));
            return response;
        }
    }
}



3.5 响应体

3.5.1 天行基础响应
package com.pine.client.beans;

import lombok.Data;

import java.io.Serial;
import java.io.Serializable;

/**
 * 天行基础响应类
 *
 * @author pine manage
 * @since 2024-11-22
 */
@Data
public class TianXingBaseResponse implements Serializable {
    @Serial
    private static final long serialVersionUID = 4154999614348985895L;

    private Long code;
    private String msg;

}

3.5.2 热点新闻响应
package com.pine.client.beans;

import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.Serial;
import java.io.Serializable;
import java.util.List;

/**
 * 天行热点新闻查询响应
 *
 * @author pine manage
 * @since 2024-11-22
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class TianXingNetHotResponse extends TianXingBaseResponse implements Serializable {
    @Serial
    private static final long serialVersionUID = 52499588383169858L;

    private List<Body> newslist;
    private String tip;

    @Data
    public static class Body implements Serializable {
        @Serial
        private static final long serialVersionUID = -1264805102673130063L;
        private String brief;
        private String index;
        private String keyword;
        private String trend;
    }
}

3.6 代理类

注意:代理的使用,我这里没做测试,如果你的应用场景涉及到了,建议先测试下。在使用代理的时候,@FeignClient 注解中的 url参数就要去掉,使用代理的proxiedUrl传入。

3.6.1 代理工厂
package com.pine.client.proxy;

import java.net.InetSocketAddress;
import java.net.Proxy;

/**
 * 代理工厂
 *
 * @author pine manage
 * @since 2024-11-22
 */
public class ProxyFactory {
    public static Proxy newHttpProxy(String hostname, int port) {
        return new Proxy(Proxy.Type.HTTP,
                new InetSocketAddress(hostname, port));
    }

    public static Proxy newDirectProxy(String hostname, int port) {
        return new Proxy(Proxy.Type.DIRECT,
                new InetSocketAddress(hostname, port));
    }

    public static Proxy newSocksProxy(String hostname, int port) {
        return new Proxy(Proxy.Type.SOCKS,
                new InetSocketAddress(hostname, port));
    }
}
3.6.2 代理客户端
package com.pine.client.proxy;

import feign.Client;
import feign.Request;
import feign.Response;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.Proxy;

/**
 * 代理客户端
 *
 * @author pine manage
 * @since 2024-11-22
 */
public class ProxyClient extends Client.Proxied {

    public ProxyClient(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier, Proxy proxy) {
        super(sslContextFactory, hostnameVerifier, proxy);
    }

    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
        return super.execute(request, options);
    }


}

3.6.3 FeignClient的建造器
package com.pine.client.proxy;

import feign.Feign;
import feign.Request;
import feign.Retryer;
import feign.codec.Decoder;
import feign.codec.Encoder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.cloud.openfeign.DefaultFeignLoggerFactory;
import org.springframework.cloud.openfeign.support.SpringMvcContract;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import java.net.Proxy;
import java.time.Duration;
import java.util.Optional;

/**
 * FeignClient建造器
 *
 * @author pine manage
 * @since 2024-11-22
 */
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class FeignClientBuilder {

    private static final Long DEFAULT_CONNECT_TIMEOUT = 5L;
    private static final Long DEFAULT_READ_TIMEOUT = 10L;
    private static final boolean DEFAULT_FOLLOW_REDIRECTS = true;

    @Nullable
    private Encoder encoder;
    @Nullable
    private Decoder decoder;
    @NonNull
    private Proxy proxy;
    @Nullable
    private SSLSocketFactory sslContextFactory;
    @Nullable
    private HostnameVerifier hostnameVerifier;
    @Nullable
    private Retryer retryer;
    @Nullable
    private Long connectTimeout;
    @Nullable
    private Long readTimeout;
    @Nullable
    private Boolean followRedirects;

    public <T> T build(Class<T> clazz, String proxiedUrl) {
        this.connectTimeout = Optional.ofNullable(this.connectTimeout).orElse(DEFAULT_CONNECT_TIMEOUT);
        this.readTimeout = Optional.ofNullable(this.readTimeout).orElse(DEFAULT_READ_TIMEOUT);
        this.followRedirects = Optional.ofNullable(this.followRedirects).orElse(DEFAULT_FOLLOW_REDIRECTS);
        this.retryer = Optional.ofNullable(retryer).orElse(Retryer.NEVER_RETRY);
        this.encoder = Optional.ofNullable(encoder).orElse(new Encoder.Default());
        this.decoder = Optional.ofNullable(decoder).orElse(new Decoder.Default());

        Request.Options options = new Request.Options(Duration.ofSeconds(connectTimeout), Duration.ofSeconds(readTimeout), followRedirects);
        ProxyClient proxyClient = new ProxyClient(sslContextFactory, hostnameVerifier, proxy);

        return Feign.builder().client(proxyClient)
                .retryer(this.retryer)
                .options(options)
                .encoder(encoder)
                .decoder(decoder)
                .logger(new DefaultFeignLoggerFactory(null).create(clazz))
                .contract(new SpringMvcContract())
                .target(clazz, proxiedUrl);
    }

//    public static void main(String[] args) {
//        com.pine.client.core.tianxing.TianXingFeignClient tianXingFeignClient = new FeignClientBuilder()
//                .setProxy(ProxyFactory.newHttpProxy("代理地址", 1003))
//                .build(com.pine.client.core.tianxing.TianXingFeignClient.class, "localhost");
//    }
}

四、调用&测试

4.1 配置信息

spring:
  cloud:
    openfeign:
      # 启用okhttp配置
      okhttp:
        enabled: true
    loadbalancer:
      # 关闭负载重试
      retry:
        enabled: false

rpc:
  tianxing:
    key: 你自己申请的天行key
    url: http://api.tianapi.com

4.2 测试代码

在controller中添加代码:

@Resource
private TianXingFeignClient tianXingFeignClient;

@PostMapping("/test")
public ResultVo<TianXingNetHotResponse> test() {
    TianXingNetHotResponse netHotResponse = tianXingFeignClient.netHot();
    return ResultVo.success(netHotResponse);
}

4.3 调用结果

控制台输出了okhttp的拦截内容:

2024-11-22 16:04:35.177  INFO  82746 --- [http-nio-8080-exec-1] com.pine.client.config.OkHttpConfig$LoggingInterceptor.intercept(OkHttpConfig.java:39) : [logId=default] Sending request http://api.tianapi.com/nethot/index?key=你自己申请的天行key on null
Accept: */*

2024-11-22 16:04:35.477  INFO  82746 --- [http-nio-8080-exec-1] com.pine.client.config.OkHttpConfig$LoggingInterceptor.intercept(OkHttpConfig.java:43) : [logId=default] Received response for http://api.tianapi.com/nethot/index?key=你自己申请的天行key in 298.1ms
Server: nginx
Date: Fri, 22 Nov 2024 08:04:35 GMT
Content-Type: application/json;charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: *
Access-Control-Max-Age: 604800
Strict-Transport-Security: max-age=31536000

接口响应结果:省略(自行调用即可)

附录

附1:本系列文章链接
SpringCloud系列文章目录(总纲篇)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2246766.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++设计模式行为模式———中介者模式

文章目录 一、引言二、中介者模式三、总结 一、引言 中介者模式是一种行为设计模式&#xff0c; 能让你减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互&#xff0c; 迫使它们通过一个中介者对象进行合作。 中介者模式可以减少对象之间混乱无序的依赖关系&…

HarmonyOS:使用ArkWeb构建页面

一、简介 页面加载是Web组件的基本功能。根据页面加载数据来源可以分为三种常用场景&#xff0c;包括加载网络页面、加载本地页面、加载HTML格式的富文本数据。 页面加载过程中&#xff0c;若涉及网络资源获取&#xff0c;需要配置ohos.permission.INTERNET网络访问权限。 二、…

矩阵的拼接

矩阵的拼接分为横向拼接和纵向拼接 注意&#xff1a;横向拼接要求两矩阵行数相同&#xff0c;纵向拼接要求两矩阵列数相同 h o r z c a t horzcat horzcat和 v e r t c a t vertcat vertcat函数 h o r z c a t ( a , b ) horzcat(a,b) horzcat(a,b)将 a a a和 b b b横向拼接&a…

SpringCloud框架学习(第五部分:SpringCloud Alibaba入门和 nacos)

目录 十二、SpringCloud Alibaba入门简介 1. 基本介绍 2.作用 3.版本选型 十三、 SpringCloud Alibaba Nacos服务注册和配置中心 1.简介 2.各种注册中心比较 3.下载安装 4.Nacos Discovery服务注册中心 &#xff08;1&#xff09; 基于 Nacos 的服务提供者 &#xf…

Ollama vs VLLM:大模型推理性能全面测评!

最近在用本地大模型跑实验&#xff0c;一开始选择了ollama,分别部署了Qwen2.5-14B和Qwen2.5-32B&#xff0c;发现最后跑出来的实验效果很差&#xff0c;一开始一直以为prompt的问题&#xff0c;尝试了不同的prompt&#xff0c;最后效果还是一直不好。随后尝试了vllm部署Qwen2.5…

.NET9 - 新功能体验(一)

被微软形容为“迄今为止最高效、最现代、最安全、最智能、性能最高的.NET版本”——.NET 9已经发布有一周了&#xff0c;今天想和大家一起体验一下新功能。 此次.NET 9在性能、安全性和功能等方面进行了大量改进&#xff0c;包含了数千项的修改&#xff0c;今天主要和大家一起体…

LeetCode 144.二叉树的前序遍历

题目&#xff1a;给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 思路&#xff1a;根 左 右 代码&#xff1a; /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNod…

【论文阅读】WGSR

0. 摘要 0.1. 问题提出 1.超分辨率(SR)是一个不适定逆问题&#xff0c;可行解众多。 2.超分辨率(SR)算法在可行解中寻找一个在保真度和感知质量之间取得平衡的“良好”解。 3.现有的方法重建高频细节时会产生伪影和幻觉&#xff0c;模型区分图像细节与伪影仍是难题。 0.2. …

游戏引擎学习第21天

虽然没有上一节的难但是内容也很多 关于实现和使用脚本语言 以下是详细复述&#xff1a; 许多人经常问一个问题&#xff0c;反复问过好几次&#xff0c;那就是&#xff1a;是否会在项目中实现脚本语言。这个问题的具体形式通常是&#xff1a;你们会使用脚本语言吗&#xff1…

NVR接入录像回放平台EasyCVR视频融合平台加油站监控应用场景与实际功能

在现代社会中&#xff0c;加油站作为重要的能源供应点&#xff0c;面临着安全监管与风险管理的双重挑战。为应对这些问题&#xff0c;安防监控平台EasyCVR推出了一套全面的加油站监控方案。该方案结合了智能分析网关V4的先进识别技术和EasyCVR视频监控平台的强大监控功能&#…

springboot vue工资管理系统源码和答辩PPT论文

人类现已迈入二十一世纪&#xff0c;科学技术日新月异&#xff0c;经济、资讯等各方面都有了非常大的进步&#xff0c;尤其是资讯与网络技术的飞速发展&#xff0c;对政治、经济、军事、文化等各方面都有了极大的影响。 利用电脑网络的这些便利&#xff0c;发展一套工资管理系统…

部署实战(二)--修改jar中的文件并重新打包成jar文件

一.jar文件 JAR 文件就是 Java Archive &#xff08; Java 档案文件&#xff09;&#xff0c;它是 Java 的一种文档格式JAR 文件与 ZIP 文件唯一的区别就是在 JAR 文件的内容中&#xff0c;多出了一个META-INF/MANIFEST.MF 文件META-INF/MANIFEST.MF 文件在生成 JAR 文件的时候…

RabbitMQ4:work模型

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…

SpringMVC——简介及入门

SpringMVC简介 看到SpringMVC这个名字&#xff0c;我们会发现其中包含Spring&#xff0c;那么SpringMVC和Spring之间有怎样的关系呢&#xff1f; SpringMVC隶属于Spring&#xff0c;是Spring技术中的一部分。 那么SpringMVC是用来做什么的呢&#xff1f; 回想web阶段&#x…

鸿蒙开发-文件与分享

文件分类 按所有者&#xff1a; 应用文件&#xff1a;所有者为应用&#xff0c;包括应用安装文件、应用资源文件、应用缓存文件等。 用户文件&#xff1a;所有者是登录到该终端设备的用户&#xff0c;包括用户私有的图片、视频、音频、文档等。 系统文件&#xff1a;与应用和…

内存级文件原理——Linux

目录 进程与文件 Linux下的文件系统 文件操作&#xff0c;及文件流 C语言函数 文件流 文件描述符 系统调用操作 系统调用参数 重定向与文件描述符 输出重定向 输入重定向 文件内容属性 Linux下一切皆文件 进程与文件 当我们对文件进行操作时&#xff0c;文件必…

KubeSphere 最佳实战:K8s 构建高可用、高性能 Redis 集群实战指南

首发&#xff1a;运维有术。 本指南将逐步引导您完成以下关键任务&#xff1a; 安装 Redis&#xff1a;使用 StatefulSet 部署 Redis。自动或手动配置 Redis 集群&#xff1a;使用命令行工具初始化 Redis 集群。Redis 性能测试&#xff1a;使用 Redis 自带的 Benchmark 工具进…

apr共享内存

下载&#xff1a; Download - The Apache Portable Runtime Project 编译&#xff1a; 使用cmake-gui生成库&#xff1a; apr-1.lib aprapp-1.lib libapr-1.lib libaprapp-1.lib libapr-1.dll 在Developer PowerShell for VS 2019中&#xff1a; 执行nmake -f Makefile.win来…

Javaweb前端HTML css 整体布局

最后一个是线条颜色 盒子&#xff0c;整体还是300&#xff0c;400

5.STM32之通信接口《精讲》之USART通信---实验串口接收程序

根据上节&#xff0c;我们一已经完成了串口发送程序的代码&#xff0c;并且深入的解析探索了串口的原理&#xff0c;接下来&#xff0c;Whappy小编将带领大家进入串口接收程序的探索与实验&#xff0c;并将结合上一节串口发送一起来完成串口的发送和接收实验。 上来两张图 上图…