在API網關spring cloud gateway和負載均衡框架ribbon實戰文章中,主要實現網關與負載均衡等基本功能,詳見代碼。本節內容將繼續圍繞此代碼展開,主要講解spring cloud gateway自定義過濾器的功能。本節內容的代碼也會提交到GitHub上,注意提交的內容。html
本節主要講解全局過濾器和局部過濾器。注意下面的示例不能做爲生產環境的代碼,只是簡單的演示自定義過濾器的使用方式,無需糾結實現的功能是否完善。下面主要針對不一樣的過濾器選擇幾種場景進行代碼演示,不表明某個場景就必須使用全局或者局部的過濾器。java
一、限流:每分鐘只能訪問5次服務react
二、接口用時統計git
一、簡單的權限檢查github
二、指定IP訪問web
本節主要演示全局過濾器的用法:實現 GlobalFilter 和 Ordered,重寫相關方法,加入到spring容器管理便可,無需配置,全局過濾器對全部的路由都有效。spring
package com.yefengyu.gateway.globalFilter; import io.github.bucket4j.Bandwidth; import io.github.bucket4j.Bucket; import io.github.bucket4j.Bucket4j; import io.github.bucket4j.Refill; 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; import java.time.Duration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; //全局過濾器,實現GlobalFilter接口,和Ordered接口便可。 @Component public class FluidControlGlobalGatewayFilter implements GlobalFilter, Ordered { int capacity = 5;//桶的最大容量,即能裝載 Token 的最大數量 int refillTokens = 1; //每次 Token 補充量 Duration duration = Duration.ofSeconds(1); //補充 Token 的時間間隔 private static final Map<String, Bucket> BUCKET_CACHE = new ConcurrentHashMap<>(); private Bucket createNewBucket() { Refill refill = Refill.greedy(refillTokens, duration); Bandwidth limit = Bandwidth.classic(capacity, refill); return Bucket4j.builder().addLimit(limit).build(); } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress(); Bucket bucket = BUCKET_CACHE.computeIfAbsent(ip, k -> createNewBucket()); System.out.println("IP: " + ip + ",has Tokens: " + bucket.getAvailableTokens()); if (bucket.tryConsume(1)) { return chain.filter(exchange); } else { exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS); return exchange.getResponse().setComplete(); } } @Override public int getOrder() { return -1000; } }
注意在pom.xml文件中加入依賴數據庫
<dependency> <groupId>com.github.vladimir-bukhtoyarov</groupId> <artifactId>bucket4j-core</artifactId> <version>4.4.1</version> </dependency>
只須要在親請求處理以前和以後標記時間便可。注意此處演示的是使用配置類的形式:app
package com.yefengyu.gateway.globalFilter; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import reactor.core.publisher.Mono; //全局過濾器,使用配置類形式,直接構造bean,使用註解完成Ordered接口功能,統計接口調用時間 @Configuration public class GlobalGatewayFilterConfig { @Bean @Order(-100) public GlobalFilter elapsedGlobalFilter() { return (exchange, chain) -> { //調用請求以前統計時間 Long startTime = System.currentTimeMillis(); return chain.filter(exchange).then().then(Mono.fromRunnable(() -> { //調用請求以後統計時間 Long endTime = System.currentTimeMillis(); System.out.println( exchange.getRequest().getURI().getRawPath() + ", cost time : " + (endTime - startTime) + "ms"); })); }; } }
權限檢查通常把信息存儲在某處,請求到來以後進行覈對,有權限的請求將真正執行。負載均衡
一、首先編寫一個工具類,對權限作管理。
package com.yefengyu.gateway.utitls; import java.util.HashMap; import java.util.Map; public final class AuthUtil { private static Map<String, String> map = new HashMap<>(); private AuthUtil() { } //程序啓動的時候加載權限的信息,好比從文件、數據庫中加載 public static void init() { map.put("tom", "123456"); } //簡單判斷 public static boolean isPermitted(String name, String password) { return map.containsKey(name) && map.get(name).equals(password); } }
咱們簡單的將權限信息放到map中保管,init方法是初始化方法,isPermitted是對外提供一個判斷是否有權限的方法。
二、服務啓動的時候,須要初始化權限map,所以主啓動類進行了修改:
package com.yefengyu.gateway; import com.yefengyu.gateway.utitls.AuthUtil; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationListener; @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication springApplication = new SpringApplication(GatewayApplication.class); springApplication.addListeners(new ApplicationListenerStarted());//增長監聽器 springApplication.run(args); } private static class ApplicationListenerStarted implements ApplicationListener<ApplicationStartedEvent> { @Override public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) { //權限初始化數據 AuthUtil.init(); } } }
三、編寫一個局部過濾器,須要實現GatewayFilter, Ordered,實現相關的方法
package com.yefengyu.gateway.localFilter; import com.yefengyu.gateway.utitls.AuthUtil; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; public class AuthGatewayFilter implements GatewayFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //獲取header的參數 String name = exchange.getRequest().getHeaders().getFirst("name"); String password = exchange.getRequest().getHeaders().getFirst("password"); boolean permitted = AuthUtil.isPermitted(name, password);//權限比較 if (permitted) { return chain.filter(exchange); } else { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } } @Override public int getOrder() { return 10; } }
四、接着須要把上面自定義的局部過濾器加入到過濾器工廠,而且註冊到spring容器中。
package com.yefengyu.gateway.localFilter; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.stereotype.Component; @Component public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> { @Override public GatewayFilter apply(Object config) { return new AuthGatewayFilter(); } }
五、在配置文件中進行配置,若是不配置則不啓用此過濾器規則。
咱們的需求是若是在配置文件配置了一個IP,那麼該ip就能夠訪問,其它IP統統不能訪問。若是不使用該過濾器,那麼全部IP均可以訪問服務。
這裏咱們看到上面的AuthGatewayFilter和AuthGatewayFilterFactory代碼原本就是爲了同一個過濾器規則編寫的兩個類,若是過濾器規則不少,那麼類文件就不少,其實這兩個類能夠合併,而且還會提供其它的功能:
package com.yefengyu.gateway.localFilter; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; @Component @Order(99) public class IPForbidGatewayFilterFactory extends AbstractGatewayFilterFactory<IPForbidGatewayFilterFactory.Config> { public IPForbidGatewayFilterFactory() { super(Config.class); } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("forbidIp"); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress(); if (config.getForbidIp().equals(ip)) { return chain.filter(exchange); } exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN); return exchange.getResponse().setComplete(); }; } static public class Config { private String forbidIp; public String getForbidIp() { return forbidIp; } public void setForbidIp(String forbidIp) { this.forbidIp = forbidIp; } } }
Config類定義了一個屬性,要重寫List<String> shortcutFieldOrder()這個方法指定屬性名稱。規則邏輯很簡單,判斷配置文件中的ip是和請求來的ip是否相同,相同則能夠訪問服務。
配置文件:
正常測試
全局過濾器,對全部的路由都有效,全部不用在配置文件中配置,主要實現了GlobalFilter 和 Ordered接口,並將過濾器註冊到spring 容器。因爲使用java配置類的方式也能夠註冊bean,全部也能夠使用配置類的方式,Ordered接口使用Order註解代替,GlobalFilter 只是個接口,能夠使用Lambda表達式替換。
局部過濾器,須要在配置文件中配置,若是配置,則該過濾器纔會生效。主要實現GatewayFilter, Ordered接口,並經過AbstractGatewayFilterFactory的子類註冊到spring容器中,固然也能夠直接繼承AbstractGatewayFilterFactory,在裏面寫過濾器邏輯,還能夠從配置文件中讀取外部數據。
本節代碼已提交:github本次提交代碼內容