springcloud动力节点-06Admin监控 Or Gateway网关
Spring Cloud Admin 监控端点
新建工程:admin-server
pom中springcloud版本号和版本控制要添加
<?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> <groupId>com.tongda</groupId> <artifactId>admin-server</artifactId> <version>0.0.1-SNAPSHOT</version> <!--定义版本号--> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-boot-admin.version>2.7.8</spring-boot-admin.version> <spring-boot.version>2.7.8</spring-boot.version> <spring-cloud.version>2021.0.5</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <!--版本控制依赖管理--> <dependencyManagement> <dependencies> <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>de.codecentric</groupId> <artifactId>spring-boot-admin-dependencies</artifactId> <version>${spring-boot-admin.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!--springcloud组件--> <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> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring-boot.version}</version> <configuration> <mainClass>com.tongda.Application</mainClass> <skip>true</skip> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
application.yml
# 应用服务 WEB 访问端口 server: port: 10086 # 端口号范围 0-65535 spring: application: name: admin-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka instance: hostname: localhost instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} management: # 管理Admin监控,暴漏信息点 endpoints: web: exposure: include: '*' # 暴漏所有监控端点
默认暴漏
在需要被监控的模块下添加pom依赖,如 order-service,user-service
<!--Admin监控,暴漏自身检查端点endPoints,一个依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>2.7.8</version> </dependency>
启动类
package com.tongda; import de.codecentric.boot.admin.server.config.EnableAdminServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient @EnableAdminServer public class AdminServerApplication { public static void main(String[] args) { SpringApplication.run(AdminServerApplication.class, args); } }
启动测试
技巧:
显示映射类、接口、地址等信息,二次开发时能够让你快速了解项目
Spring Cloud Gateway
1.什么是网关
2.Spring Cloud Gateway 简介
4.Spring Cloud Gateway 三大核心概念
4.1 Route(路由)(重点 和 eureka 结合做动态路由)
4.2 Predicate(断言:就是一个返回 bool 的表达式)
4.3 Filter(过滤) (重点)
5.Nginx 和 Gateway 的区别
6.Gateway 快速入门
6.1 本次访问流程
6.2 新建项目选择依赖(不要选 web)
此处注意选择Gateway不能选择Web,因为Web使用tomcat,Gateway使用的Netty。
6.3 修改启动类
@SpringBootApplication @EnableEurekaClient //网关也是 eureka 的客户端 public class Gateway80Application { public static void main(String[] args) { SpringApplication.run(Gateway80Application.class, args); } }
6.4 修改配置文件
server: port: 80 # 网关一般是80 spring: application: name: gateway-server cloud: gateway: enabled: true # 开启网关只要加载依赖,默认开启 routes: # 设置路由,注意是数组,可以设置多个,按照id做隔离 - id: login-server-route # 这个是路由的ID,保持唯一即可 uri: http://localhost:8081 # uri:统一资源定位符,url:统一资源标识符 predicates: # 断言匹配 - path=/doLogin # 和服务中的路径匹配,正则匹配规则模式,只要你path匹配上了/doLogin就往uri转发,并且uri路径后:IP:8081/doLogin - id: login2-server-route # 路由id,没有要求,保持唯一即可 uri: http://localhost:8082 predicates: - path=/dologin2/** # 如果匹配到第一个路由,则第二个就不会走了,注意这不是负载均衡。 #eureka 的配置 eureka: instance: instance-id: ${spring.application.name}:${server.port} prefer-ip-address: true client: service-url: defaultZone: http://localhost:8761/eureka/
6.5 启动测试
7.Gateway 集群
7.1 创建两个 gateway,端口分别为 80 和 81
server: port: 80 # 网关一般是80 spring: application: name: gateway-server cloud: gateway: enabled: true # 开启网关只要加载依赖,默认开启 routes: # 设置路由,注意是数组,可以设置多个,按照id做隔离 - id: login-server-route # 这个是路由的ID,保持唯一即可 uri: http://localhost:8081 # 此处设置真正的服务ip:port,uri:统一资源定位符,url:统一资源标识符 predicates: # 断言匹配 - path=/doLogin # 和服务中的路径匹配,正则匹配规则模式,只要你path匹配上了/doLogin就往uri转发,并且uri路径后:IP:8081/doLogin - id: login2-server-route # 路由id,没有要求,保持唯一即可 uri: http://localhost:8082 predicates: - path=/dologin2/** # 如果匹配到第一个路由,则第二个就不会走了,注意这不是负载均衡。 # eureka 的配置 eureka: instance: instance-id: ${spring.application.name}:${server.port} prefer-ip-address: true client: service-url: defaultZone: http://localhost:8761/eureka/
7.2 Nginx 的配置文件修改
1、nginx要配置2个tomcat
8.Gateway 的两种路由配置方式
8.1 代码路由方式(掌握)
8.1.1 创建配置类 GatewayConfig
@SpringBootApplication public class DemogatewayApplication { @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("path_route", r -> r.path("/get") .uri("http://httpbin.org")) .route("host_route", r -> r.host("*.myhost.org") .uri("http://httpbin.org")) .route("rewrite_route", r -> r.host("*.rewrite.org") .filters(f -> f.rewritePath("/foo/(?<segment>.*)", "/${segment}")) .uri("http://httpbin.org")) .route("hystrix_route", r -> r.host("*.hystrix.org") .filters(f -> f.hystrix(c -> c.setName("slowcmd"))) .uri("http://httpbin.org")) .route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org") .filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback"))) .uri("http://httpbin.org")) .route("limit_route", r -> r .host("*.limited.org").and().path("/anything/**") .filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter()))) .uri("http://httpbin.org")) .build(); } }
创建配置类
package com.tongda.config; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RouteConfig { @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("path_route", r -> r.path("/get") .uri("http://httpbin.org")) .route("host_route", r -> r.host("*.myhost.org") .uri("http://httpbin.org")) .route("rewrite_route", r -> r.host("*.rewrite.org") .filters(f -> f.rewritePath("/foo/(?<segment>.*)", "/${segment}")) .uri("http://httpbin.org")) .route("hystrix_route", r -> r.host("*.hystrix.org") .filters(f -> f.hystrix(c -> c.setName("slowcmd"))) .uri("http://httpbin.org")) .route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org") .filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback"))) .uri("http://httpbin.org")) .route("limit_route", r -> r .host("*.limited.org").and().path("/anything/**") .filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter()))) .uri("http://httpbin.org")) .build(); }
@Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("guochuang-id",r->r.path("/guochuang").uri("https://www.bilibili.com/")) .route("dance-id",r->r.path("/v/dance").uri("https://www.bilibili.com/")) .route("test-id",r->r.path("/v/kichiku").uri("https://www.bilibili.com/")) .build();
9.Gateway 微服务名动态路由,负载均衡
9.1 概述
9.2 最佳实践
9.2.1 修改 gateway 配置
动态路由+注册中心实现访问
导入Eureka依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
注册Eureka
# eureka 的配置 eureka: instance: instance-id: ${spring.application.name}:${server.port} prefer-ip-address: true client: service-url: defaultZone: http://localhost:8761/eureka/ registry-fetch-interval-seconds: 3 #网关拉取服务列表的时间缩短
开启动态路由
discovery: locator: enabled: true #开启动态路由,开启通用应用名称,找到服务的功能 lower-case-service-id: true #开启服务名称小写
完整的application.yml
server: port: 80 # 网关一般是80 spring: application: name: gateway-server cloud: gateway: # 如服务里有100个路径,但做负载均衡,要配动态路由结合Eureka注册中心 enabled: true # 开启网关只要加载依赖,默认开启 routes: # 设置路由,注意是数组,可以设置多个,按照id做隔离, - id: login-server-route # 这个是路由的ID,保持唯一即可 uri: http://localhost:8081 # 此处设置真正的服务ip:port,uri:统一资源定位符,url:统一资源标识符 predicates: # 断言匹配 - path=/doLogin # 和服务中的路径匹配,正则匹配规则模式,只要你path匹配上了/doLogin就往uri转发,并且uri路径后:IP:8081/doLogin - id: login2-server-route # 路由id,没有要求,保持唯一即可 uri: http://localhost:8082 predicates: # 如服务中有100个路径 - path=/dologin2/** # 如果匹配到第一个路由,则第二个就不会走了,注意这不是负载均衡。 discovery: locator: enabled: true #开启动态路由,开启通用应用名称,找到服务的功能 lower-case-service-id: true #开启服务名称小写 # eureka 的配置 eureka: instance: instance-id: ${spring.application.name}:${server.port} prefer-ip-address: true client: service-url: defaultZone: http://localhost:8761/eureka/ registry-fetch-interval-seconds: 3 #网关拉取服务列表的时间缩短
02-login-service
Pom.xml添加依赖
开启Eureka
启动服务
访问时动态路由需要+上域名称login-service
实现服务均衡、服务发现
另一种方法:uri修改
URI:lb://login-service 包含了http://localhost:8081和8082
实现负载均衡、轮询的效果。
lb:// 协议写法:负载均衡
10. Predicate 断言工厂的使用【了解】
10.1 什么是断言,Gateway 里面有哪些断言
10.2 如何使用这些断言
server: port: 80 # 网关一般是80 spring: application: name: gateway-server cloud: gateway: # 如服务里有100个路径,但做负载均衡,要配动态路由结合Eureka注册中心 enabled: true # 开启网关只要加载依赖,默认开启 routes: # 设置路由,注意是数组,可以设置多个,按照id做隔离, - id: login-server-route # 这个是路由的ID,保持唯一即可 uri: http://localhost:8081 # 此处设置真正的服务ip:port,uri:统一资源定位符,url:统一资源标识符 predicates: # 断言匹配,断言是给某一路由来设定的 - path=/doLogin # 匹配规则,和服务中的路径匹配,正则匹配规则模式,只要你path匹配上了/doLogin就往uri转发,并且uri路径后:IP:8081/doLogin - id: login2-server-route # 路由id,没有要求,保持唯一即可 uri: http://localhost:8082 predicates: # 断言是给某一路由来设定的,如服务中有100个路径 - path=/dologin2/** # 如果匹配到第一个路由,则第二个就不会走了,注意这不是负载均衡。 - After=2020-01-20T17:42:47.789-07:00[Asia/Shanghai] #此断言匹配发生在指定日期时间之后的请求,ZonedDateTime dateTime=ZonedDateTime.now()获得 - Before=2020-06-18T21:26:26.711+08:00[Asia/Shanghai] #此断言匹配发生在指定日期时间之前的请求 - Between=2020-06-18T21:26:26.711+08:00[Asia/Shanghai],2020-06-18T21:32:26.711+08:00[Asia/Shanghai] #此断言匹配发生在指定日期时间之间的请求 - Cookie=name,xiaobai #Cookie 路由断言工厂接受两个参数,Cookie 名称和 regexp(一 个 Java 正则表达式)。此断言匹配具有给定名称且其值与正则表达式匹配的 cookie - Header=token,123456 #头路由断言工厂接受两个参数,头名称和 regexp(一个 Java 正则表达式)。此断言与具有给定名称的头匹配,该头的值与正则表达式匹配。 - Host=**.bai*.com:* #主机路由断言工厂接受一个参数:主机名模式列表。该模式是一个 ant 样式的模式。作为分隔符。此断言匹配与模式匹配的主机头 - Method=GET,POST #方法路由断言工厂接受一个方法参数,该参数是一个或多个参数:要匹配的 HTTP 方法 - Query=username,cxs #查询路由断言工厂接受两个参数:一个必需的 param 和一个可选的 regexp(一个 Java 正则表达式)。 - RemoteAddr=192.168.1.1/24 #RemoteAddr 路由断言工厂接受一个源列表(最小大小 1),这些源是 cidr 符号(IPv4 或 IPv6)字符串,比如 192.168.1.1/24(其中 192.168.1.1 是 IP 地址,24 是子网掩码)。 discovery: locator: enabled: true #开启动态路由,开启通用应用名称,找到服务的功能 lower-case-service-id: true #开启服务名称小写 # eureka 的配置 eureka: instance: instance-id: ${spring.application.name}:${server.port} prefer-ip-address: true client: service-url: defaultZone: http://localhost:8761/eureka/ registry-fetch-interval-seconds: 3 #网关拉取服务列表的时间缩短
spring: cloud: gateway: routes: - id: weight_high uri: https://weighthigh.org predicates: - Weight=group1,8 - id: weight_low uri: https://weightlow.org predicates: - Weight=group1,2
10.3 断言总结
10.4.1 最佳实践
10.4.1.2 修改配置文件
10.4.1.3 访问测试是否进入自定义断言器
11. Filter 过滤器工厂(重点)
11.1 概述
11.2 分类
11.2.1 按生命周期分两种
11.2.2 按种类分也是两种
11.3 官方文档查看过滤器
11.3.1 单一过滤器(31 个)
11.3.2 全局过滤器(9 个)
11.4 自定义网关过滤器(重点)
11.4.1 自定义全局过滤器
11.4.2 创建配置类 GlobalFilterConfig
package com.tongda.filter; import jdk.nashorn.internal.parser.Token; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * * @Version 1.0 */ @Component @Slf4j public class GlobalfilterConfig implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("进入我自己的全局过滤器"); String token = exchange.getRequest().getQueryParams().getFirst("token"); if (token == null) { log.error("token为空,没有认证"); exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE); return exchange.getResponse().setComplete(); } log.info("验证通过"); return chain.filter(exchange); } /** * order 越小越先执行 * @param * @return {@link int} * **/ @Override public int getOrder() { return 0; } }
MyGlobalFilter
package com.tongda.filter; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.bouncycastle.util.Bytes; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.HashMap; /** * 这个及时过滤的方法 * 过滤器链模式 * 责任链模式 * * */ @Component public class MyGlobalFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 针对请求的过滤,拿到请求header url参数。。。。 ServerHttpRequest request = exchange.getRequest(); // HttpServletRequest 这个是web里面的 // ServerHttpRequest webFlux里面,响应式里面的 String path = request.getURI().getPath(); System.out.println(path); HttpHeaders headers = request.getHeaders(); System.out.println(headers); String methodName = request.getMethod().name(); System.out.println(methodName); String hostName = request.getRemoteAddress().getHostName(); System.out.println(hostName); // 响应相关的数据 ServerHttpResponse response = exchange.getResponse(); // 用了微服务,肯定是前后端分离的,前后端分离,一般前后通过json // {"code":200,"msg":"ok"} // 设置编码,响应头里面设置 response.getHeaders().set("content-type","application/json;charset=utf-8"); // 组装业务返回值 HashMap<String, Object> map = new HashMap<>(4); map.put("code", HttpStatus.UNAUTHORIZED.value());// 取状态码 map.put("msg","你未授权"); ObjectMapper objectMapper = new ObjectMapper(); // 把一个map转成字节 byte[] bytes = new byte[0]; try { bytes = objectMapper.writeValueAsBytes(map); } catch (JsonProcessingException e) { e.printStackTrace(); } // 通过buffer工厂将字节数组包装成一个数据包 DataBuffer wrap = response.bufferFactory().wrap(bytes); return response.writeWith(Mono.just(wrap)); // 传入exchange放行,到下一个过滤器 // return chain.filter(exchange); } /** * 排序 * 指定顺序的方法,越小越先执行,如-1 ~ -500 * @param * @return {@link int} * @throws * **/ @Override public int getOrder() { return 0; } }
11.4.3 访问测试
12. IP 认证拦截实战
12.1 创建 IPGlobalFilter
package com.tongda.filter; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.HashMap; import java.util.Map; /** * * @Version 1.0 */ @Component @Slf4j public class IPGlobalFilter implements GlobalFilter, Ordered { @SneakyThrows @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String ip = exchange.getRequest().getHeaders().getHost().getHostName(); //这里写死,只做演示 if (ip.equals("localhost")){ //说明是黑名单里面的ip ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); Map<String, Object> map = new HashMap<>(); map.put("conde",HttpStatus.UNAUTHORIZED); map.put("msg","非法访问"); response.getHeaders().add("content-type","application/json;charset=UTF-8"); ObjectMapper objectMapper = new ObjectMapper(); byte[] bytes = objectMapper.writeValueAsBytes(map); DataBuffer wrap = response.bufferFactory().wrap(bytes); return response.writeWith(Mono.just(wrap)); } return chain.filter(exchange); } // 设置此过滤器的执行顺序 @Override public int getOrder() { return 1; } }
IPcheckFilter
package com.tongda.filter; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.Arrays; import java.util.HashMap; import java.util.List; /** * IP黑名单拦截 * 网关里面 过滤器 * IP拦截,请求都有一个源头 * 电话 144 027 010 * 请求--->gateway--->service * 根据数量,像具体的业务服务,一般黑名单 * 黑名单:black_list * 白名单:white_list 数据库 * */ @Component public class IPCheckFilter implements GlobalFilter, Ordered { /** * 网关的并发比较高,不要再网关里面直接操作mysql * 后台系统可以查询数据库,用户量,并发量不大 * 如果并发量大,可以查redis或者在内存中写好 * * * @param exchange * @param chain * @return {@link Mono< Void>} * * @date 2023/12/3 8:11 **/ public static final List<String> BLACK_LIST = Arrays.asList("127.0.0.1","115.128.232.147"); // 拿到IP // 校验ip合法 // 放行/拦截 @SneakyThrows @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest();// 获取请求头 String ip = request.getHeaders().getHost().getHostString();// 获取ip // 查询数据库,看这个ip是否存在黑名单里面。 // 只要是能存储数据的地方都叫数据库redis mysql if (!BLACK_LIST.contains(ip)) { // 符合放行 return chain.filter(exchange); } // 拦截 ServerHttpResponse response = exchange.getResponse(); // 响应 response.getHeaders().set("content-type","application/json;charset=utf-8"); HashMap<Object, Object> map = new HashMap<>(4); map.put("code",438);// 状态码438 map.put("msg","你是黑名单"); // 转换器 ObjectMapper mapper = new ObjectMapper(); byte[] bytes = mapper.writeValueAsBytes(map);// 字节数组 DataBuffer wrap = response.bufferFactory().wrap(bytes); return response.writeWith(Mono.just(wrap)); } @Override public int getOrder() { return 0; } }
12.2 测试访问
13. 限流实战(会问)
完善login-service,不需数据库假登录
创建User用户类
package com.tongda.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * */ @Data @AllArgsConstructor @NoArgsConstructor public class User { private Integer id; private String name; private String pwd; private Integer age; }
模拟登录,添加依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>3.2.0</version> </dependency>
无数据登录
LoginController 类中
package com.tongda.controller; import com.tongda.domain.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.time.Duration; import java.util.UUID; @RestController public class LoginController { @Resource public StringRedisTemplate stringRedisTemplate; @GetMapping("doLogin") public String doLogin(String name,String pwd){ System.out.println(name); System.out.println(pwd); // 假设去做了登录 User user = new User(1,name,pwd,18); // token 登录获取 String token = UUID.randomUUID().toString(); // 存起来,set设置token,user.toString,时间小时3600,1天是86400秒 stringRedisTemplate.opsForValue().set(token,user.toString(), Duration.ofSeconds(7200)); return token; } }
01-gateway-server中
Pom中添加redis依赖
创建Filter拦截器
TokenCheckFilter配置类
package com.tongda.filter; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.io.StringReader; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; /** * 前提是?和前端约定好,一般放在请求头里面,一般key:Authorization授权 value:bearer前缀token中 * 1.拿到请求url * 2.判断放行 * 3.拿到请求头 * 4.拿到token * 5.校验 * 6.放行/拦截 * */ public class TokenCheckFilter implements GlobalFilter, Ordered { // 由于很多地址,只放允许的路径。 public static final List<String> ALLOW_URL = Arrays.asList("/login-service/doLogin","myUrl"); @Autowired private StringRedisTemplate redisTemplate; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 拿请求url,路径 ServerHttpRequest request = exchange.getRequest(); String path = request.getURI().getPath(); // 判断,是否包含匹配路径 if (ALLOW_URL.contains(path)) { return chain.filter(exchange); // 包含就放行 } // 检查 HttpHeaders headers = request.getHeaders(); // 拿请求头 List<String> authorization = headers.get("Authorization"); // 与前端约定好的 // 判断 if (!CollectionUtils.isEmpty(authorization)){ // 非空时 String token = authorization.get(0); // 非空,取索引0 if (StringUtils.hasText(token)){ // 再判断value中有前端约定 // 约定好的前缀的bearer token String realToken = token.replaceFirst("bearer", ""); // 判断,值不为空,并且redis有相同值 if (StringUtils.hasText(realToken)&& redisTemplate.hasKey(realtoken)){ return chain.filter(exchange); } } } // 拦截 ServerHttpResponse response = exchange.getResponse(); response.getHeaders().set("content-type","application/json;charset=utf-8"); HashMap<Object, Object> map = new HashMap<>(4); // 返回401 map.put("code", HttpStatus.UNAUTHORIZED.value()); map.put("msg","未授权"); ObjectMapper objectMapper = new ObjectMapper(); byte[] bytes = new byte[0]; try { bytes = objectMapper.writeValueAsBytes(map); } catch (JsonProcessingException e) { e.printStackTrace(); } DataBuffer wrap = response.bufferFactory().wrap(bytes); return response.writeWith(Mono.just(wrap)); } @Override public int getOrder() { return 0; } }
测试,搭建一个服务,提供接口03-teacher-service
pom.xml中添加依赖Eureka
启动类中@EnableEurekaClient启动
创建controller中TeacherController
package com.tongda.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @RestController public class TeacherController { @GetMapping("teacher") public String Teacher() { return "教书学习"; } }
放行
测试工具,如果没有带token测试失败报401
将这个值取到放到token的value值中
网关过滤搞定
13.1 什么是限流
13.2 本次限流模型
13.3 Gateway 结合 redis 实现请求量限流
13.3.1 添加依赖
<!-- 限流https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> <version>3.2.0</version> </dependency>
创建配置RequestLimitConfig类
package com.tongda.config; import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import reactor.core.publisher.Mono; /** * */ @Configuration public class RequestLimitConfig { // 针对某一个接口ip来限流 /doLogin,每一个ip10s只能访问3次 // keyResolver:key解析器 @Bean("ipKeyResolver") // bean的名字默认就是方法名 public KeyResolver ipkeyResolver() { // 获取IP地址 return exchange -> Mono.just(exchange.getRequest().getHeaders().getHost().getHostString()); } // 针对这个路径来限制 /doLogin 10秒内只能被访问3次,3次后只能到下一个10s // api,就是接口,外面一般把gateway叫api网关、新一代网关 @Bean public KeyResolver apiKeyResolver(){ return exchange -> Mono.just(exchange.getRequest().getPath().value()); } }
13.3.2 修改配置文件
server: port: 80 # 网关一般是80 spring: application: name: gateway-server cloud: gateway: # 如服务里有100个路径,但做负载均衡,要配动态路由结合Eureka注册中心 enabled: true # 开启网关只要加载依赖,默认开启 routes: # 设置路由,注意是数组,可以设置多个,按照id做隔离, - id: login-server-route # 这个是路由的ID,保持唯一即可 uri: http://localhost:8081 # 此处设置真正的服务ip:port,uri:统一资源定位符,url:统一资源标识符 predicates: # 断言匹配,断言是给某一路由来设定的 - path=/doLogin # 匹配规则,和服务中的路径匹配,正则匹配规则模式,只要你path匹配上了/doLogin就往uri转发,并且uri路径后:IP:8081/doLogin - id: login2-server-route # 路由id,没有要求,保持唯一即可 uri: http://localhost:8082 predicates: # 断言是给某一路由来设定的,如服务中有100个路径 - path=/dologin2/** # 如果匹配到第一个路由,则第二个就不会走了,注意这不是负载均衡。 - After=2020-01-20T17:42:47.789-07:00[Asia/Shanghai] #此断言匹配发生在指定日期时间之后的请求,ZonedDateTime dateTime=ZonedDateTime.now()获得 # - Before=2020-06-18T21:26:26.711+08:00[Asia/Shanghai] #此断言匹配发生在指定日期时间之前的请求 # - Between=2020-06-18T21:26:26.711+08:00[Asia/Shanghai],2020-06-18T21:32:26.711+08:00[Asia/Shanghai] #此断言匹配发生在指定日期时间之间的请求 # - Cookie=name,xiaobai #Cookie 路由断言工厂接受两个参数,Cookie 名称和 regexp(一 个 Java 正则表达式)。此断言匹配具有给定名称且其值与正则表达式匹配的 cookie # - Header=token,123456 #头路由断言工厂接受两个参数,头名称和 regexp(一个 Java 正则表达式)。此断言与具有给定名称的头匹配,该头的值与正则表达式匹配。 # - Host=**.bai*.com:* #主机路由断言工厂接受一个参数:主机名模式列表。该模式是一个 ant 样式的模式。作为分隔符。此断言匹配与模式匹配的主机头 - Method=GET,POST #方法路由断言工厂接受一个方法参数,该参数是一个或多个参数:要匹配的 HTTP 方法 # - Query=username,cxs #查询路由断言工厂接受两个参数:一个必需的 param 和一个可选的 regexp(一个 Java 正则表达式)。 # - RemoteAddr=192.168.1.1/24 #RemoteAddr 路由断言工厂接受一个源列表(最小大小 1),这些源是 cidr 符号(IPv4 或 IPv6)字符串,比如 192.168.1.1/24(其中 192.168.1.1 是 IP 地址,24 是子网掩码)。 filters: - name: RequestRatelimiter # 这个是过滤器的名称 ages: # 这个过滤器的参数 key-resolver: '#{@hostAddrKeyResolver}' # 通过spel表达式取ioc容器中bean的值 redis-rate-limiter.replenishRate: 1 # 生成令牌的速度 redis-rate-limiter.burstCapacity: 3 # 桶容量 discovery: locator: enabled: true #开启动态路由,开启通用应用名称,找到服务的功能 lower-case-service-id: true #开启服务名称小写 # eureka 的配置 eureka: instance: instance-id: ${spring.application.name}:${server.port} prefer-ip-address: true client: service-url: defaultZone: http://localhost:8761/eureka/ registry-fetch-interval-seconds: 3 #网关拉取服务列表的时间缩短
server: port: 80 spring: application: name: gateway-80 cloud: gateway: enabled: true routes: - id: user-service uri: lb://consumer-user-service predicates: - Path=/info/** filters: - name: RequestRatelimiter # 这个是过滤器的名称 ages: # 这个过滤器的参数 key-resolver: '#{@hostAddrKeyResolver}' # 通过spel表达式取ioc容器中bean的值 redis-rate-limiter.replenishRate: 1 # 生成令牌的速度 redis-rate-limiter.burstCapacity: 3 # 桶容量 redis: #redis 的配置 host: 192.168.226.128 port: 6379 database: 0 eureka: instance: instance-id: ${spring.application.name}:${server.port} prefer-ip-address: true client: service-url: defaultZone: http://localhost:8761/eureka/
13.3.3 配置文件说明
13.3.4 创建配置类 RequestRateLimiterConfig
package com.tongda.config; import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import reactor.core.publisher.Mono; /** * @Author JunLong * @Date 2023/12/11 7:47 * @Version 1.0 */ @Configuration public class RequestLimitConfig { // 针对某一个接口ip来限流 /doLogin,每一个ip10s只能访问3次 // keyResolver:key解析器 @Bean("ipKeyResolver") // bean的名字默认就是方法名 @Primary // 当启动报错;2个bean时,使用 主候选的 注解 public KeyResolver ipkeyResolver() { // 获取IP地址 return exchange -> Mono.just(exchange.getRequest().getHeaders().getHost().getHostString()); } // 针对这个路径来限制 /doLogin 10秒内只能被访问3次,3次后只能到下一个10s // api,就是接口,外面一般把gateway叫api网关、新一代网关 @Bean public KeyResolver apiKeyResolver(){ return exchange -> Mono.just(exchange.getRequest().getPath().value()); } }
package com.tongda.config; import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import reactor.core.publisher.Mono; public class RequestLimitConfig1 { /* * IP限流 * 把用户的IP作为限流的key * * @param null * @return {@link null} * **/ @Bean @Primary // 主候选 public KeyResolver hostAddrkeyResolver() { return (exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName())); } // 用户ID限流 // 把用户id作为限流的key @Bean public KeyResolver userKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst('userId')); } // 请求接口限流 // 把请求的路径作为限流Key @Bean public KeyResolver apiKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getPath().value()); } }
13.3.5 启动快速访问测试
14. 跨域配置
package com.tongda.filter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsWebFilter; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; import org.springframework.web.util.pattern.PathPatternParser; // 跨域:放开所有 // 第二种是在:yaml写跨域 @Configuration public class CorsConfig { @Bean public CorsWebFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedMethod("*"); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser()); source.registerCorsConfiguration("/**", config); return new CorsWebFilter(source); } }
server:
port: 80 # 网关一般是80
spring:
application:
name: gateway-server
cloud:
gateway: # 如服务里有100个路径,但做负载均衡,要配动态路由结合Eureka注册中心
enabled: true # 开启网关只要加载依赖,默认开启
routes: # 设置路由,注意是数组,可以设置多个,按照id做隔离,
- id: login-server-route # 这个是路由的ID,保持唯一即可
uri: http://localhost:8081 # 此处设置真正的服务ip:port,uri:统一资源定位符,url:统一资源标识符
predicates: # 断言匹配,断言是给某一路由来设定的
- path=/doLogin # 匹配规则,和服务中的路径匹配,正则匹配规则模式,只要你path匹配上了/doLogin就往uri转发,并且uri路径后:IP:8081/doLogin
- id: login2-server-route # 路由id,没有要求,保持唯一即可
uri: http://localhost:8082
predicates: # 断言是给某一路由来设定的,如服务中有100个路径
- path=/dologin2/** # 如果匹配到第一个路由,则第二个就不会走了,注意这不是负载均衡。
- After=2020-01-20T17:42:47.789-07:00[Asia/Shanghai] #此断言匹配发生在指定日期时间之后的请求,ZonedDateTime dateTime=ZonedDateTime.now()获得
# - Before=2020-06-18T21:26:26.711+08:00[Asia/Shanghai] #此断言匹配发生在指定日期时间之前的请求
# - Between=2020-06-18T21:26:26.711+08:00[Asia/Shanghai],2020-06-18T21:32:26.711+08:00[Asia/Shanghai]
#此断言匹配发生在指定日期时间之间的请求
# - Cookie=name,xiaobai #Cookie 路由断言工厂接受两个参数,Cookie 名称和 regexp(一 个 Java 正则表达式)。此断言匹配具有给定名称且其值与正则表达式匹配的 cookie
# - Header=token,123456 #头路由断言工厂接受两个参数,头名称和 regexp(一个 Java 正则表达式)。此断言与具有给定名称的头匹配,该头的值与正则表达式匹配。
# - Host=**.bai*.com:* #主机路由断言工厂接受一个参数:主机名模式列表。该模式是一个 ant 样式的模式。作为分隔符。此断言匹配与模式匹配的主机头
- Method=GET,POST #方法路由断言工厂接受一个方法参数,该参数是一个或多个参数:要匹配的 HTTP 方法
# - Query=username,cxs #查询路由断言工厂接受两个参数:一个必需的 param 和一个可选的 regexp(一个 Java 正则表达式)。
# - RemoteAddr=192.168.1.1/24 #RemoteAddr 路由断言工厂接受一个源列表(最小大小 1),这些源是 cidr 符号(IPv4 或 IPv6)字符串,比如 192.168.1.1/24(其中 192.168.1.1 是 IP 地址,24 是子网掩码)。
filters:
- name: RequestRatelimiter # 这个是过滤器的名称
ages: # 这个过滤器的参数
key-resolver: '#{@hostAddrKeyResolver}' # 通过spel表达式取ioc容器中bean的值
redis-rate-limiter.replenishRate: 1 # 生成令牌的速度
redis-rate-limiter.burstCapacity: 3 # 桶容量
discovery:
locator:
enabled: true #开启动态路由,开启通用应用名称,找到服务的功能
lower-case-service-id: true #开启服务名称小写
# 跨域设置
globalcors: # 全局跨越配置
cors-configurations:
'[/**]':
allowCredentials: true # 可以携带cookie跨域
allowedHeaders: # 头
- '*'
allowedMethods: # 方法
- '*'
allowedOrigins: # 源
- '*'
# eureka 的配置
eureka:
instance:
instance-id: ${spring.application.name}:${server.port}
prefer-ip-address: true
client:
service-url:
defaultZone: http://localhost:8761/eureka/
registry-fetch-interval-seconds: 3 #网关拉取服务列表的时间缩短
spring: cloud: gateway: globalcors: corsConfigurations: '[/**]': // 针对哪些路径 allowCredentials: true // 这个是可以携带 cookie allowedHeaders: '*' allowedMethods: '*' allowedOrigins: '*'
15. 总结和面试
15.1 Gateway 和 zuul 的区别 ZuulFilter
15.2 Nginx 在微服务中的地位
15.3 关于限流,面试不会直接问,而是间接来问 问 不卖超
15.4 健康状态检查等
<!-- 健康检查的依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
management: endpoints: web: exposure: include: '*' #暴露检查的端点