OpenFeign 使用教程:从入门到实践

文章目录

  • 一、什么是 OpenFeign?
    • 1、什么是 OpenFeign?
    • 2、什么是 Feign?
    • 3、OpenFeign 与 Feign 的关系
    • 4、为什么选择 OpenFeign?
    • 5、总结
  • 二、OpenFeign 的使用步骤
    • 1. 导入依赖
    • 2. 启用 OpenFeign
    • 3. 配置 Nacos
  • 三、`@FeignClient` 参数详解
    • 1. `name` / `value`
    • 2. `url`
    • 3. `configuration`
    • 4. `fallback`
    • 5. `fallbackFactory`
    • 6. `path`
    • 7. `contextId`
  • 四、完整实例:带 `fallbackFactory` 和 `configuration` 的 Feign 客户端
    • 1. 引入依赖
    • 2. 配置 Nacos
    • 3. 自定义配置类-configuration
    • 4. 自定义降级工厂类
    • 5. 定义 Feign 客户端-fallbackFactory
    • 6. 使用 `InnerAuth` 注解
    • 7. 调用 Feign 客户端
    • 8. 验证效果


一、什么是 OpenFeign?

OpenFeign 是一个声明式的 Web 服务客户端,它使得编写 HTTP 客户端变得更加简单。只需要创建一个接口并添加注解,就可以完成对远程服务的调用。OpenFeign 集成了 Ribbon 和 Hystrix(可选),支持负载均衡和服务熔断。

在微服务架构中,服务之间的调用是常见的需求。为了简化这种跨服务的调用,OpenFeignFeign 提供了一种声明式的 HTTP 客户端解决方案。本文将详细介绍 OpenFeign 的简介,并深入探讨它与 Feign 的关系。


1、什么是 OpenFeign?

OpenFeign 是 Spring Cloud 生态系统中的一个组件,是对 Netflix Feign 的增强和扩展。它是一种声明式的 Web 服务客户端,允许开发者通过定义接口和注解的方式轻松实现对远程服务的调用。

核心特点:

  1. 声明式接口:通过简单的接口和注解(如 @FeignClient)定义服务调用逻辑,无需手动编写 HTTP 请求代码。
  2. 集成 Spring:OpenFeign 深度集成了 Spring 框架,支持 Spring 的依赖注入、配置管理等功能。
  3. 负载均衡:内置 Ribbon 支持,能够自动实现客户端负载均衡。
  4. 熔断器支持:可与 Hystrix 集成,提供服务降级和熔断功能。
  5. 灵活扩展:支持自定义拦截器、编码器、解码器等,满足个性化需求。

2、什么是 Feign?

Feign 是由 Netflix 开发的一个轻量级 HTTP 客户端库,它最初设计用于简化 RESTful API 的调用。Feign 的核心思想是通过定义接口和注解的方式,将 HTTP 请求抽象为 Java 接口方法调用。

核心特点:

  1. 声明式接口:与 OpenFeign 类似,Feign 也通过接口和注解定义服务调用逻辑。
  2. 轻量化:Feign 是一个独立的库,不依赖于任何框架。
  3. 可插拔性:支持多种编码器、解码器和日志记录器,可以根据需要进行扩展。
  4. 社区活跃:虽然 Netflix 已停止维护 Feign,但其开源版本仍然被广泛使用。

3、OpenFeign 与 Feign 的关系

  1. 继承与扩展
  • OpenFeign 是基于 Feign 的扩展版本,它继承了 Feign 的核心功能,并在此基础上增加了对 Spring Cloud 生态的支持。
  • Feign 是一个独立的 HTTP 客户端库,而 OpenFeign 则是 Spring Cloud 对 Feign 的封装和增强。
  1. Spring Cloud 的整合
  • Feign:是一个通用的 HTTP 客户端,适用于任何 Java 应用程序,但它本身并不与 Spring 框架深度集成。
  • OpenFeign:专门为 Spring Cloud 设计,提供了与 Spring 的无缝集成能力。例如,支持 Spring 的依赖注入、配置文件管理、负载均衡(Ribbon)、服务发现(Eureka/Nacos)以及熔断器(Hystrix)等功能。
  1. 功能对比
功能FeignOpenFeign
Spring 集成不支持支持
负载均衡需要手动配置内置 Ribbon 支持
服务发现需要手动实现支持 Eureka/Nacos 等注册中心
熔断器支持需要手动集成 Hystrix内置 Hystrix 支持
日志级别配置需要手动配置支持 Spring 的日志配置
社区维护Netflix 停止维护Spring 社区持续维护
  1. 代码差异
  • 使用 Feign
import feign.Feign;
import feign.Logger;
import feign.gson.GsonDecoder;public class FeignExample {public static void main(String[] args) {ExampleClient client = Feign.builder().decoder(new GsonDecoder()).logger(new Logger.ErrorLogger()).logLevel(Logger.Level.BASIC).target(ExampleClient.class, "http://example.com");String response = client.getData("param");System.out.println(response);}
}interface ExampleClient {@RequestLine("GET /api/example?param={param}")String getData(@Param("param") String param);
}
  • 使用 OpenFeign
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;@FeignClient(name = "example-service", url = "http://example.com")
public interface ExampleClient {@GetMapping("/api/example")String getData(@RequestParam("param") String param);
}

从代码可以看出:

  • Feign 的配置更加繁琐,需要手动设置解码器、日志记录器等。
  • OpenFeign 利用了 Spring 的特性,配置更加简洁,且支持注解驱动。

4、为什么选择 OpenFeign?

  1. 更强大的生态支持
    OpenFeign 作为 Spring Cloud 的一部分,能够无缝集成 Spring 的各种功能,如负载均衡、服务发现、熔断器等。

  2. 更高的开发效率
    OpenFeign 的声明式接口和注解方式极大地简化了服务调用的开发过程,减少了手动编写 HTTP 请求代码的工作量。

  3. 更好的社区维护
    虽然 Feign 已经停止维护,但 OpenFeign 作为 Spring Cloud 的一部分,得到了 Spring 社区的持续支持和更新。

  4. 更适合微服务架构
    OpenFeign 专为微服务设计,能够轻松应对服务间的复杂调用场景。


5、总结

Feign 是一个轻量级的 HTTP 客户端库,适合简单的 RESTful API 调用。而 OpenFeign 是 Feign 的增强版,专注于微服务架构,深度集成了 Spring Cloud 生态,提供了更强大的功能和更高的开发效率。

如果你正在使用 Spring Cloud 构建微服务架构,那么 OpenFeign 是一个更好的选择;而如果你需要一个独立的 HTTP 客户端库,Feign 仍然是一个不错的选择。

希望这篇博客能帮助你更好地理解 OpenFeign 和 Feign 的关系!如果有任何问题,欢迎随时交流!


二、OpenFeign 的使用步骤

1. 导入依赖

在使用 OpenFeign 之前,需要确保项目中已经引入了相关的依赖。以下是 Maven 项目的依赖配置:

<!-- Spring Boot Starter -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><!-- Spring Cloud OpenFeign -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency><!-- Nacos 作为注册中心 -->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

同时,确保在 pom.xml 中指定了 Spring Cloud 的版本,例如:

<dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2022.0.3</version> <!-- 根据实际情况选择版本 --><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>

2. 启用 OpenFeign

在 Spring Boot 的启动类上添加 @EnableFeignClients 注解,以启用 OpenFeign 功能:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication
@EnableFeignClients // 启用 Feign 客户端
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

3. 配置 Nacos

application.yml 中配置 Nacos 作为注册中心:

spring:application:name: consumer-service # 当前服务名称cloud:nacos:discovery:server-addr: localhost:8848 # Nacos 地址
server:port: 8081

三、@FeignClient 参数详解

好的!下面我们将详细讲解 @FeignClient 注解的参数,并结合 Nacos 作为配置中心和注册中心的实际场景,给出完整的实例。

@FeignClient 是 OpenFeign 的核心注解,用于声明一个 Feign 客户端。以下是其常用参数的详细说明:

1. name / value

  • 作用:指定服务名称,通常与注册中心(如 Nacos)中的服务名一致。

  • 示例

    @FeignClient(name = "example-service")
    public interface ExampleClient {@GetMapping("/api/example")String getExampleData(@RequestParam("param") String param);
    }
    

    在上述代码中,name = "example-service" 表示该客户端会调用名为 example-service 的服务。


2. url

  • 作用:指定服务的直接 URL(如果未使用注册中心)。

  • 注意:当使用注册中心(如 Nacos)时,通常不需要显式指定 url,因为服务发现机制会自动解析服务地址,也可以不用name只用url,这样就不走注册中心,直接使用url访问。

  • 示例

    @FeignClient(name = "example-service", url = "http://localhost:8080")
    public interface ExampleClient {@GetMapping("/api/example")String getExampleData(@RequestParam("param") String param);
    }
    

3. configuration

  • 作用:自定义 Feign 客户端的配置类。

  • 用途:可以自定义拦截器、编码器、解码器等。

  • 示例
    首先,创建一个自定义配置类:

    import feign.Logger;
    import org.springframework.context.annotation.Bean;public class FeignConfig {@BeanLogger.Level feignLoggerLevel() {return Logger.Level.FULL; // 设置日志级别为 FULL}
    }
    

    然后,在 @FeignClient 中引用该配置类:

    @FeignClient(name = "example-service", configuration = FeignConfig.class)
    public interface ExampleClient {@GetMapping("/api/example")String getExampleData(@RequestParam("param") String param);
    }
    

4. fallback

  • 作用:指定熔断器的降级处理类。

  • 用途:当远程服务不可用时,提供备用逻辑。

  • 示例
    创建一个降级类:

    import org.springframework.stereotype.Component;@Component
    public class ExampleClientFallback implements ExampleClient {@Overridepublic String getExampleData(String param) {return "Fallback response for param: " + param;}
    }
    

    @FeignClient 中引用降级类:

    @FeignClient(name = "example-service", fallback = ExampleClientFallback.class)
    public interface ExampleClient {@GetMapping("/api/example")String getExampleData(@RequestParam("param") String param);
    }
    

5. fallbackFactory

  • 作用:指定熔断器的降级工厂类。

  • 优点:相比 fallback,可以捕获异常信息。

  • 示例
    创建一个降级工厂类:

    import feign.hystrix.FallbackFactory;
    import org.springframework.stereotype.Component;@Component
    public class ExampleClientFallbackFactory implements FallbackFactory<ExampleClient> {@Overridepublic ExampleClient create(Throwable cause) {return new ExampleClient() {@Overridepublic String getExampleData(String param) {return "Fallback response due to: " + cause.getMessage();}};}
    }
    

    @FeignClient 中引用降级工厂类:

    @FeignClient(name = "example-service", fallbackFactory = ExampleClientFallbackFactory.class)
    public interface ExampleClient {@GetMapping("/api/example")String getExampleData(@RequestParam("param") String param);
    }
    

6. path

  • 作用:指定基础路径,所有接口方法都会继承该路径。

  • 示例

    @FeignClient(name = "example-service", path = "/api/v1")
    public interface ExampleClient {@GetMapping("/example")String getExampleData(@RequestParam("param") String param);
    }
    

    上述代码中,实际请求路径为 /api/v1/example


7. contextId

  • 作用:指定上下文 ID,用于区分多个同名的 Feign 客户端。

  • 场景:当项目中有多个同名的 Feign 客户端时,需要通过 contextId 区分。

  • 示例

    @FeignClient(name = "example-service", contextId = "client1")
    public interface ExampleClient1 {@GetMapping("/api/example")String getExampleData(@RequestParam("param") String param);
    }@FeignClient(name = "example-service", contextId = "client2")
    public interface ExampleClient2 {@GetMapping("/api/example")String getExampleData(@RequestParam("param") String param);
    }
    

四、完整实例:带 fallbackFactoryconfiguration 的 Feign 客户端

以下是一个完整的实例,包含以下功能:

  1. 使用 fallbackFactory 实现服务降级。
  2. configuration 中添加 Token 到请求头中。
  3. 使用 InnerAuth 注解实现内部服务认证。

1. 引入依赖

pom.xml 中添加以下依赖:

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

2. 配置 Nacos

application.yml 中配置 Nacos 作为注册中心:

spring:application:name: consumer-service # 当前服务名称cloud:nacos:discovery:server-addr: localhost:8848 # Nacos 地址
server:port: 8081

3. 自定义配置类-configuration

创建一个自定义配置类,用于向请求头中添加 Token:

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class FeignConfig {@Beanpublic RequestInterceptor requestInterceptor() {return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate template) {template.header("Authorization", "Bearer your-token");}};}
}

4. 自定义降级工厂类

创建一个降级工厂类,用于捕获异常并返回备用响应:

import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;@Component
public class ExampleClientFallbackFactory implements FallbackFactory<ExampleClient> {@Overridepublic ExampleClient create(Throwable cause) {return new ExampleClient() {@Overridepublic String getDataFromProvider(String param) {return "Fallback response due to: " + cause.getMessage();}};}
}

5. 定义 Feign 客户端-fallbackFactory

创建一个 Feign 客户端,调用注册在 Nacos 中的服务:

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;@FeignClient(name = "provider-service",configuration = FeignConfig.class,fallbackFactory = ExampleClientFallbackFactory.class
)
public interface ExampleClient {@GetMapping("/api/provider")String getDataFromProvider(@RequestParam("param") String param);
}

6. 使用 InnerAuth 注解

加了@InnerAuth注解每次会先进去到InnerAuthAspect.java处理,验证请求头是否为from-source,且携带内部标识参数inner。如果非内部请求访问会直接抛出异常。 但是网关访问的时候,也可以手动带上这个from-source参数,来达到这个目的

假设我们有一个自定义的 @InnerAuth 注解,用于标记只允许内部服务调用的接口:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InnerAuth {
}

7. 调用 Feign 客户端

在业务逻辑中,可以通过依赖注入的方式使用定义的 Feign 客户端:

在控制器中使用该注解:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
public class ConsumerController {@Autowiredprivate ExampleClient exampleClient;@InnerAuth@GetMapping("/call-provider")public String callProvider(@RequestParam("param") String param) {return exampleClient.getDataFromProvider(param);}
}

8. 验证效果

  1. 启动 Nacos 服务。
  2. 启动 provider-service,确保其注册到 Nacos。
  3. 启动 consumer-service,调用 /call-provider 接口,验证是否成功调用了 provider-service
  4. 模拟服务不可用,验证 fallbackFactory 是否生效。

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

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

相关文章

蓝桥杯 16.对局匹配

对局匹配 原题目链接 题目描述 小明喜欢在一个围棋网站上找别人在线对弈。这个网站上所有注册用户都有一个积分&#xff0c;代表他的围棋水平。 小明发现&#xff0c;网站的自动对局系统在匹配对手时&#xff0c;只会将积分差恰好是 K 的两名用户匹配在一起。如果两人分差小…

C#常用LINQ

在开发时发现别人的代码使用到了LINQ十分便捷且清晰&#xff0c;这里记录一下常用LINQ和对应的使用。参考链接&#xff1a;LINQ 菜鸟教程 使用的学生类和字符串用于测试 public class Student {public int StudentID;public string StudentName;public int Age; }Student[] st…

单例模式(线程安全)

1.什么是单例模式 单例模式&#xff08;Singleton Pattern&#xff09;是一种创建型设计模式&#xff0c;旨在确保一个类只有一个实例&#xff0c;并提供一个全局访问点来访问该实例。这种模式涉及到一个单一的类&#xff0c;该类负责创建自己的对象&#xff0c;同时确保只有单…

Python 之 __file__ 变量导致打包 exe 后路径输出不一致的问题

现象 做项目的时候&#xff0c;一直使用 os.path.dirname(os.path.abspath(__file__)) 来获取当前目录。然而&#xff0c;最近却遇到了一个路径相关的问题。直接运行 py 文件是正常的&#xff0c;但是打包成 exe 之后&#xff0c;却显示因为路径问题导致程序报错无法继续执行。…

PH热榜 | 2025-04-21

1. Google Whisk 2.0 标语&#xff1a;将图像转换为八秒的动画短片。 介绍&#xff1a;Whisk 是谷歌实验室的一项新创新&#xff0c;现在推出了 Whisk Animate——它可以将你的图片转换成生动的8秒视频&#xff0c;采用了 Veo 2 技术。此功能现已在60多个国家的 Google One A…

AI大模型 —— 国产大模型 —— 华为大模型

有这么一句话&#xff0c;那就是AI大模型分两种&#xff0c;一种是大模型&#xff1b;另一种是华为大模型。 如果从技术角度来分析&#xff0c;华为的技术不论是在软件还是硬件都比国外的大公司差距极大&#xff0c;甚至有些技术评论者认为华为的软硬件技术至少落后2.5代&#…

FPGA 中 XSA、BIT 和 DCP 文件的区别

在 FPGA&#xff08;现场可编程门阵列&#xff09;开发中&#xff0c;XSA、BIT 和 DCP 文件是常见的文件类型&#xff0c;它们在功能、用途、文件内容等方面存在明显区别&#xff0c;以下是详细介绍&#xff1a; 1. XSA 文件 定义与功能 XSA&#xff08;Xilinx Shell Archiv…

MH2103系列coremark1.0跑分数据和优化,及基于arm2d的优化应用

CoreMark 1.0 介绍 CoreMark 是由 EEMBC&#xff08;Embedded Microprocessor Benchmark Consortium&#xff09;组织于 2009 年推出的一款用于衡量嵌入式系统 CPU 或 MCU 性能的标准基准测试工具。它旨在替代陈旧的 Dhrystone 标准&#xff08;Dhrystone 容易受到各种libc不同…

云原生与AI的关系是怎么样的?

云原生与AI的结合正在重塑现代应用的开发与部署模式&#xff0c;两者相辅相成&#xff0c;共同推动技术创新与产业升级。以下是两者的核心概念、结合点及未来趋势的详细解析&#xff1a; 一、云原生与AI的核心概念 云原生&#xff08;Cloud Native&#xff09; • 定义&#…

【CentOs】构建云服务器部署环境

(一) 服务器采购 2 CPU4G 内存40G 系统盘 80G 数据盘 (二) 服务器安全组和端口配置 (三) 磁盘挂载 1 登录 root 2 查看目前磁盘使用情况 df -h 3 查看磁盘挂载情况 识别哪些磁盘没挂载 fdisk -l 4 对未挂载磁盘做分区 fdisk /dev/vdb 输入m&#xff0…

LangChain4j语言模型选型指南:主流模型能力全景对比

LangChain4j语言模型选型指南&#xff1a;主流模型能力全景对比 前言 在大语言模型应用开发中&#xff0c;选择合适的底层模型提供商是架构设计的关键决策。LangChain4j作为Java生态的重要AI框架&#xff0c;其支持的20模型提供商各有独特的优势场景。本文通过功能矩阵深度解…

2025.4.21日学习笔记 JavaScript String、Array、date、math方法的使用

1. String&#xff08;字符串&#xff09; String 对象用于处理和操作文本数据。 length&#xff1a;返回字符串的长度。 const str "Hello"; console.log(str.length); // 输出: 5 charAt(index)&#xff1a;返回指定索引位置的字符。 const str "Hello…

(14)VTK C++开发示例 --- 将点投影到平面上

文章目录 1. 概述2. CMake链接VTK3. main.cpp文件4. 演示效果 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;VTK开发 &#x1f448; 1. 概述 计算一个点在一个平面上的投影。 vtkPlane 是 VTK&#xff08;Visualization Toolkit&#xff09;库中的一个类&…

电子电器架构 ---软件定义汽车的电子/电气(E/E)架构

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 周末洗了一个澡,换了一身衣服,出了门却不知道去哪儿,不知道去找谁,漫无目的走着,大概这就是成年人最深的孤独吧! 旧人不知我近况,新人不知我过…

Android开发中的复制和粘贴

Android 提供了一个强大的基于剪贴板的框架&#xff0c;用于复制和粘贴。它支持简单和复杂的数据类型&#xff0c;包括文本字符串、复杂数据结构、文本和二进制流数据&#xff0c;以及应用资源。简单的文本数据直接存储在剪贴板中&#xff0c;而复杂的数据则存储为引用&#xf…

【STM32单片机】#10.5 串口数据包

主要参考学习资料&#xff1a; B站江协科技 STM32入门教程-2023版 细致讲解 中文字幕 开发资料下载链接&#xff1a;https://pan.baidu.com/s/1h_UjuQKDX9IpP-U1Effbsw?pwddspb 单片机套装&#xff1a;STM32F103C8T6开发板单片机C6T6核心板 实验板最小系统板套件科协 实验&…

百度暑期实习岗位超3000个,AI相关岗位占比87%,近屿智能携AIGC课程加速人才输出

今年3月&#xff0c;百度重磅发布3000暑期实习岗位&#xff0c;聚焦大模型、机器学习、自动驾驶等AI方向的岗位比例高达87%。此次实习岗位涉及技术研发、产品策划、专业服务、管理支持、政企解决方案等四大类别&#xff0c;覆盖超300个岗位细分方向。值得一提的是&#xff0c;百…

vue3 + element-plus中el-dialog对话框滚动条回到顶部

对话框滚动条回到顶部 1、需要对话框显示后 2、使用 nextTick 等待 Dom 更新完毕 3、通过开发者工具追查到滚动条对应的标签及class“el-overlay-dialog” 4、设置属性 scrollTop 0 或者 执行方法 scrollTo(0, 0) // 对话框显示标识 const dialogVisible ref(false); //…

C++学习之游戏服务器开发十一DOCKER的基本使用

目录 1.多实例部署方案 2.容器的概念 3.docker初识 4.docker仓库 5.docker镜像 6.docker容器 7.docker和虚拟机的区别 8.docker命令解释 9.dockerfile构建镜像 10.离线分发镜像 1.多实例部署方案 redis 命令&#xff08; redis-cli XXXX &#xff09; set key value:…

2025.4.21总结

工作&#xff1a;开了一场关于大模型版本的会议&#xff0c;回归一个问题单&#xff0c;提了两个单&#xff0c;把用例都执行完。如今都四月中旬了&#xff0c;上班年快要结束了&#xff0c;该到了冲刺KPI的时候了。 今日思考&#xff1a;刷到了jack叔叔的视频&#xff0c;讲了…