1.前言
根據教材、博客文章的實例實操,基本都是單層攔截,沒有找到多層攔截的具體寫法 ,讓我走了很多彎路,我將其寫在這里,以待以后參考。
2.環境
spring boot : 2.1.6.RELEASE
spring cloud : Greenwich.SR2
3.准備一個端口為5001 的 Zuul網關
目錄結構

導入依賴
<!--zuul 網關-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
pom文件
<?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>cen.cloud</groupId> <artifactId>cen-mycloud</artifactId> <version>0.0.1-SNAPSHOT</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>zuul-server-5001</artifactId> <version>0.0.1-SNAPSHOT</version> <name>zuul-server-5001</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <!--==========================================================================--> <!--eureka 注冊中心依賴包 --> <!-- 這是服務中心端的依賴包--> <!-- <dependency>--> <!-- <groupId>org.springframework.cloud</groupId>--> <!-- <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>--> <!-- </dependency>--> <!-- 可是服務客戶端的依賴包,兩個包都有一樣的功能-發現服務,但是下面這個包無 服務端的注解--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--==========================================================================--> <!--zuul 網關--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
application.properties 文件 ,設置自定義映射,路徑含有/bd/則路由到 百度首頁 ,用於測試
spring.application.name=zuul-server-5001 server.port=5001 # eureka.client.service-url.defaultZone=http://localhost:7001/eureka/ #全局添加前綴,如 localhost:114/myzuul/test/bb ,用於識別是否需要轉發路由操作 #不可使用 /zuul ,猜測這是保留字 zuul.prefix=/mzuul #//默認是false,這里是全局配置 #zuul.strip-prefix: //是否將這個代理前綴去掉 # #忽略所有的,表示禁用默認路由,只認我們自己配置的路由. #zuul.ignored-services="*" # #自定義路由設置 #攔截路徑 zuul.routes.bd.path=/bd/** #攔截后訪問的指定地址 zuul.routes.bd.url=https://www.baidu.com/ # ##攔截路徑 #zuul.routes.CONSUMER-9001.path=/CONSUMER-9001/** ##攔截后訪問的指定地址 #zuul.routes.CONSUMER-9001.service-id=CONSUMER-9001 # #攔截后訪問的指定服務,使用服務名,根據注冊中心獲取的服務列表映射具體服務ip地址 #zuul.routes.api-b.service-id=520LOVE #http://localhost:5001/mzuul/CONSUMER-9001/gettname # 心得 :如果使用 服務列表默認映射的 攔截路徑 ,則寫在 httpurl 的服務名必須小寫 ,即便 遠程服務名 有大小寫字符 , # 但在請求路徑也必須全部改成小寫 ,否則報錯 404
第一層攔截 LoginFilter文件
package com.example.zuulserver5001.myFilter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.apache.commons.lang.StringUtils; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.io.UnsupportedEncodingException; //注冊bean ,不使用@Component注解則需要去啟動類創建一個方法new一個LoginFilter類后 // 使用 @bean注解注冊bean,一樣的作用 @Component public class LoginFilter extends ZuulFilter { /** * 選擇過濾器類型 */ @Override public String filterType() { //一共有下面4種過濾器 // public static final String ERROR_TYPE = "error"; // public static final String POST_TYPE = "post"; // public static final String PRE_TYPE = "pre"; // public static final String ROUTE_TYPE = "route"; return FilterConstants.PRE_TYPE; } /** * 通過返回的int值來定義過濾器的執行順序,數字越小優先級越高 */ @Override public int filterOrder() { return 0; } /** * 返回一個`Boolean`值,判斷該過濾器是否需要執行。返回true執行,返回false不執行。 */ @Override public boolean shouldFilter() { return true; } /** * 過濾器的具體業務邏輯 */ @Override public Object run() throws ZuulException { System.out.println("進入zuul攔截-login攔截"); //獲取上下文 RequestContext ctx = RequestContext.getCurrentContext(); //獲取Request HttpServletRequest request = ctx.getRequest(); //獲取請求參數 String token = request.getParameter("token"); System.out.println("參數token=" + token); // // if (StringUtils.isBlank(token)) { //參數內容為空 //攔截,拒絕路由 ctx.setSendZuulResponse(false); //返回狀態碼 ctx.setResponseStatusCode(401); //返回的響應體信息 try { //不可以直接寫中文,前端會顯示中文亂碼,加上這就解決中文亂碼問題 //以文本格式顯示,字體比較大 ctx.getResponse().setContentType("text/html;charset=UTF-8"); //以json格式顯示,字體比較小 // ctx.getResponse().setContentType("application/json;charset=UTF-8"); // 上一句等同於 ctx.getResponse().setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); // ctx.getResponse().getWriter().write("token is 空的-------401"); } catch (IOException e) { e.printStackTrace(); } } return null; } }
第二層攔截 LoginCheckFilter文件
package com.example.zuulserver5001.myFilter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.apache.commons.lang.StringUtils; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.logging.Filter; @Component public class LoginCheckFilter extends ZuulFilter { //上下文,不可以設為 private RequestContext ctx = null; //Request private HttpServletRequest request = null; @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return 1; } @Override public boolean shouldFilter() { System.out.println("進入zuul攔截-loginCheck攔截,判斷是否開啟該攔截"); //獲取上下文 ctx = RequestContext.getCurrentContext(); // 判斷上一層攔截是否通過 if(!ctx.sendZuulResponse()){ //上一層攔截不通過 System.out.println(" 上一層攔截不通過,不開啟loginCheck攔截"); //該攔截不需要開啟 return false; } //上層攔截通過 //獲取Request request = ctx.getRequest(); //獲取請求路徑 String urlStr = request.getRequestURI().toString(); //當訪問路徑含有/mzuul/bd/則開啟該攔截 return urlStr.contains("/mzuul/bd"); } @Override public Object run() throws ZuulException { System.out.println("運行loginCheck攔截邏輯"); //獲取請求參數 String token = request.getParameter("token"); System.out.println("---參數token=" + token); if (StringUtils.isBlank(token) | (token != null && !token.equals("kk"))) { // token 是空的 或者 不是 kk //攔截 System.out.println("攔截,拒絕路由請求, token 是空的 或者 不是 kk"); ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(7781); try { ctx.getResponse().setContentType("text/html;charset=UTF-8"); ctx.getResponse().getWriter().write("請求參數="+token+",當參數是kk才可以通過"); } catch (IOException e) { e.printStackTrace(); } } return null; } }
啟動類
package com.example.zuulserver5001; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; import org.springframework.cloud.netflix.zuul.EnableZuulServer; @SpringBootApplication //開啟發現服務 @EnableEurekaClient //開啟zuul網關代理 @EnableZuulProxy public class ZuulServer5001Application { public static void main(String[] args) { SpringApplication.run(ZuulServer5001Application.class, args); } }
圖中指示的文件是熔斷回路操作 ,在另一篇隨筆詳細講解,這里不解釋,因為用不上

4.測試
瀏覽器 訪問 端口5001 ,http://localhost:5001/mzuul/bd


可見被第一層攔截了並判定不通過 ,進入了第二次攔截時被判斷不開啟第二次過濾
再次訪問 http://localhost:5001/mzuul/bd?token=ghj


可見第一層攔截通過了 ,進入第二層攔截 被判定了開啟第二次過濾 ,在第二次過濾 操作判定為不通過
再次訪問 http://localhost:5001/mzuul/bd?token=kk


可見成功將請求路由到了 百度首頁 ,通過了第一、二層過濾 ,參數符合要求 ,舉一反三 ,可以在攔截邏輯加入身份認證等操作 ,如加入shiro 、 security等安全框架配合使用
5.其他心得
(1)如果配合eureka ,可使用遠程服務名的全小寫為路由關鍵字來映射到指定的遠程服務去,
如果遠程服務名 為 conSUmer-9001
不可使用
#http://localhost:5001/mzuul/CONSUMER-9001/gettname
或
#http://localhost:5001/mzuul/conSUmer-9001/gettname
必須寫成全小寫
#http://localhost:5001/mzuul/consumer-9001/gettname
但是必須開啟默認路由 ,不寫這個配置即可 ,默認使用默認路由
#忽略所有的,表示禁用默認路由,只認我們自己配置的路由.
#zuul.ignored-services="*"
(2) 前綴不可以使用zuul
#不可使用 /zuul ,猜測這是保留字
zuul.prefix=/mzuul
(3)不論是什么請求,只有經過zuul都會執行一篇所有的過濾攔截,順序是根據filterOrder()方法的返回值來判定的 ,通過返回的正整數值來定義過濾器的執行順序,數字越小優先級越高
/** * 通過返回的int值來定義過濾器的執行順序,數字越小優先級越高 */ @Override public int filterOrder() { return 0; }
因此 ,在每層過濾文件需要做判斷這個請求是否需要開啟這個過濾的操作邏輯 ,
只要上一層有一個過濾不通過,下面的過濾都沒必要在執行,
