spring cloud Zuul 多层拦截 --- 心得

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>
View Code

 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
View Code

 

第一层拦截 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;
    }
}
View Code

 

第二层拦截 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;
    }
}
View Code

 

启动类

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);
    }

}
View Code

 

 

图中指示的文件是熔断回路操作 ,在另一篇随笔详细讲解,这里不解释,因为用不上

 

 

 

 

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;
    }

 

 

 因此 ,在每层过滤文件需要做判断这个请求是否需要开启这个过滤的操作逻辑 ,

只要上一层有一个过滤不通过,下面的过滤都没必要在执行,

 

posted @ 2020-05-31 10:48  岑惜  阅读(700)  评论(0编辑  收藏  举报