Nacos + OpenFeign +Gateway
文章目录
- Nacos
- 注册中心 + 负载均衡
- 依赖注入
- 整合配置
- 调用配置
- 配置中心
- 分环境配置
- 读取配置信息
- 总结
- OpenFeign
- 日志 + OpenFeign 配置
- 拦截器
- Gateway
- 基础配置
- 自定义断言工厂和过滤器
Nacos
nacos下载地址
# 1. 进入bin目录下打开命令行
# 2. windows启动nacos
startup.cmd -m standalone
# 2. linux启动nacos
startup.sh -m standalone
# 3. 启动后访问网址
http://localhost:8848/nacos/
项目对应版本 依赖文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<packaging>pom</packaging>
<groupId>com.hebut</groupId>
<artifactId>cloud_demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud.version>2023.0.3</spring-cloud.version>
<spring-cloud-alibaba.version>2023.0.3.2</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
注册中心 + 负载均衡
依赖注入
<!-- 服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
整合配置
- 编写配置文件 application.properties
# nacos地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 服务名称
spring.application.name=service-order
# 端口
server.port=8080
- 开启服务注册/发现功能
@EnableDiscoveryClient //服务发现核心注解
@SpringBootApplication
public class OrderMainApplication {
public static void main(String[] args) {
SpringApplication.run(OrderMainApplication.class, args);
}
}
调用配置
- 获取配置信息
@Autowired
DiscoveryClient discoveryClient;
// 获取所有服务名称
for (String service : discoveryClient.getServices()) {
System.out.println(service);
// 获取服务实例
for (ServiceInstance instance : discoveryClient.getInstances(service)) {
// 打印服务实例信息 ip + port + uri
System.out.println(instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri());
}
}
- 调用配置 + 负载均衡
@Autowired
LoadBalancerClient loadBalancerClient;
// 1. 获取商品服务所在的所有机器的 ip + port
ServiceInstance choose = loadBalancerClient.choose("service-product");
// 2. 调用商品服务
String url = "http://" + choose.getHost() + ":" + choose.getPort() + "/product/" + id;
log.info("远程调用地址: " + url);
- 另一种 调用配置 + 负载均衡
@Configuration
public class OrderServiceConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Autowired
RestTemplate restTemplate;
// 用 服务名字service-product 动态确定服务地址
String url = "http://service-product/product/" + id;
Product product = restTemplate.getForObject(url, Product.class);
配置中心
<!-- 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
分环境配置
创建 application.yml文件 删除原来的.properties文件
server:
port: 8080
spring:
application:
name: service-order
cloud:
nacos:
server-addr: 127.0.0.1:8848
config:
namespace: dev
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
读取配置信息
把所有 import的配置文件中 以 order开头的配置都获取到
@Component
@ConfigurationProperties(prefix = "order") // 外部config配置优先
@Data
public class OrderProperties {
private String timeout;
// 驼峰映射
private String autoConfirm;
private String url;
}
// 方法一 不推荐!!!
@RefreshScope // 动态刷新nacos配置文件
@RestController
public class OrderController {
// 获取nacos的配置文件内容
@Value("${order.timeout}")
String orderTimeout;
@Value("${order.auto-confirm}")
String orderAutoConfirm;
@Value("${order.url}")
String orderUrl;
// 方法二 推荐!!!
@Autowired
OrderProperties orderProperties;
@GetMapping("/config")
public String config() {
return orderProperties.getTimeout() + " ; " + orderProperties.getAutoConfirm() + " ; " + orderProperties.getUrl();
}
监听配置内容
@EnableDiscoveryClient // 开启服务注册与发现
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
ApplicationRunner applicationRunner(NacosConfigManager nacosConfigManager) {
return args -> {
nacosConfigManager.getConfigService().addListener("service-order.properties", "DEFAULT_GROUP", new Listener() {
@Override
public Executor getExecutor() {
return Executors.newFixedThreadPool(4);
}
@Override
public void receiveConfigInfo(String s) {
System.out.println("接收到配置信息:" + s);
}
});
};
}
}
总结
OpenFeign
openfeign官方文档
<!-- openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
兜底回调 只有引入和配置了 sentinel 才能生效 否则会报错
@Component
public class ProductFeignFallback implements ProductFeignClient {
@Override
public Product getProductById(Long id) {
System.out.println("兜底回调");
Product product = new Product();
product.setId(id);
product.setPrice(new BigDecimal("0"));
product.setNum(0);
product.setProductName("测试商品");
return product;
}
}
发送请求接口
@FeignClient(value = "service-product",fallback = ProductFeignFallback.class)
public interface ProductFeignClient {
// 发送请求
@GetMapping("/product/{id}")
Product getProductById(@PathVariable("id") Long id);
}
调用接口
@Autowired
ProductFeignClient productFeignClient;
Product product = productFeignClient.getProductById(productId);
需要在启动类上面加上注解 !!!
@EnableFeignClients // 开启feign远程调用客户端
日志 + OpenFeign 配置
application.yml 配置文件编写
server:
port: 8080
spring:
application:
name: service-order
cloud:
nacos:
server-addr: 127.0.0.1:8848
config:
namespace: dev
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
profiles:
# 调用resources下的其他配置文件
include: feign
logging:
level:
com.hebut.order.feign: debug
feign 配置文件编写
spring:
cloud:
openfeign:
client:
config:
default:
connectTimeout: 2000
readTimeout: 5000
# retryer: feign.Retryer.Default
@Configuration
public class OrderServiceConfig {
// feign的debug日志
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
// 连接超时或读取超时后重新发送请求的策略
@Bean
Retryer retryer() {
// 不传参数就是默认
return new Retryer.Default(100, SECONDS.toMillis(1), 5);
}
}
拦截器
以请求拦截器为例
@Component
public class TokenRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
System.out.println("TokenRequestInterceptor");
// 设置请求头的token
requestTemplate.header("token", "admin");
}
}
利用 HttpServletRequest request 获取请求头的内容
@GetMapping("/product/{id}")
public Product getProductById(@PathVariable("id") Long id,
HttpServletRequest request) {
Product product = productService.getProductById(id);
System.out.println(request.getHeader("token"));
return product;
}
Gateway
基础配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
server:
port: 80
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
profiles:
include: gateway
spring:
cloud:
gateway:
# 跨域cors
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
allowedHeaders: "*"
# 路由
routes:
# id 需要唯一
- id: order-route
# 需要访问的url
uri: lb://service-order
# 满足断言则访问上面的url
predicates:
- Path=/api/order/**
# 访问url前的过滤器
filters:
- name: StripPrefix
args:
parts: 2
- Token=token,jwt
- id: product-route
uri: lb://service-product
predicates:
- Path=/api/product/**
- id: bing-route
uri: https://cn.bing.com/
predicates:
- name: Path
args:
patterns: /search
- name: Query
args:
param: q
regexp: hello
# - Vip=user,zuo
- name: Vip
args:
param: user
value: zuo
# 全局过滤器
default-filters:
- AddResponseHeader=X-Request-Foo, Zuo
自定义断言工厂和过滤器
gateway官方文档
断言工厂
@Component
public class VipRoutePredicateFactory extends AbstractRoutePredicateFactory<VipRoutePredicateFactory.Config> {
public VipRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate(){
@Override
public boolean test(ServerWebExchange exchange) {
String vip = exchange.getRequest().getQueryParams().getFirst(config.getParam());
return vip != null && vip.equals(config.getValue());
}
};
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("param", "value");
}
public static class Config {
private String param;
private String value;
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}
过滤器
@Component
public class TokenGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// TODO: 添加token校验逻辑
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = response.getHeaders();
String value = config.getValue();
if ("uuid".equalsIgnoreCase(value)) {
value = UUID.randomUUID().toString();
}
if ("jwt".equalsIgnoreCase(value)) {
value = "jwt.Token";
}
headers.add(config.getName(), value);
}));
}
};
}
}
全局过滤器
@Component
@Slf4j
public class GlobalFilter implements org.springframework.cloud.gateway.filter.GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String url = request.getURI().getPath();
long start = System.currentTimeMillis();
log.info("请求路径:{}, 开始时间:{}",url,start);
// 前置处理
//拦截器
Mono<Void> filter = chain.filter(exchange).doFinally(s -> {
long end = System.currentTimeMillis();
// .doFinally 是后置处理
log.info("请求结束:{}, 结束时间:{}, 耗时:{}",url,end,end-start);
});
return filter;
}
@Override
public int getOrder() {
return 0;
}
}