SpringCloud-zuul

引入pom文件


    
      org.springframework.cloud
      spring-cloud-starter-ribbon
    
    
    
      org.springframework.cloud
      spring-cloud-starter-netflix-eureka-client
    
    
    
      org.springframework.cloud
      spring-cloud-starter-zuul

注意上面部分的依赖,因为网关也是基于Eureka的说一客户端一定要引入,否则网关根本跑不起来
另外网关默认也是基于ribbon的,所以也要引入。

关于代码

入口APP
很简单:

@SpringBootApplication
@EnableZuulProxy
public class App
{
    public static void main( String[] args )
    {
        SpringApplication.run(App.class, args);
    }
}

关于配置

Eurake的配置

这个是基本的配置:

server:
  port: 5000 #网关5字头,第一个

指定当前eureka客户端的注册地址,也就是eureka服务的提供方,当前配置的服务的注册服务方

eureka:
  instance:
    hostname: 172.17.7.90
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:2000/eureka

#指定应用名称

spring:
  application:
    name: zuul-agw

这个和普通的基本eureka没啥区别,有了这个网关就能跑起来,其他不说了。

关于超时

Zuul超时有两种方式,比较靠谱的方式用ribbon进行超时。Zuul是用ribbon做负载均衡的,所以配置了ribbon就配置了超时。

ribbon:
  eureka:
    enabled: true
  ReadTimeout: 3500
  ConnectTimeout: 3500

顺便说一下红色部分,红色部分很重要,如果使用eureka的服务列表,那么这个开关一定要打开,否则在eureka上线和下线的时候,通知不及时会导致服务异常!

路由配置

zuul:
  # 使用 prefix 添加前缀
  prefix: /zyth
  routes:
    eureka:
      path: /monitor-eureka/**
      url: url
	  serviceId: oneServiceName

上面就是一个最简单的配置。
说明一下,prefix是统一前缀,就是先判断能否进入这个网关代理,有这个前缀的才进入网关代理。如果不配置,就是所有的请求都经过代理。

Routes就是具体不同的二级后缀和实际转发的配置了。
Path:是匹配的模式。
模式匹配到了,就有两个转发模式:url或者serviceId
url就会转入到指定的http地址中,而serviceId就会转入到一个具体的微服务地址中。如果url后面的值,不是http等,这个地方就会当作是一个serviceID处理,也就是说,url这里直接配置serviceId也是ok的

关于统一前缀是否去掉

关于统一前缀是否去掉。
SpringCloud-zuul_第1张图片

关于路由

服务路由有两种,默认情况下,配置了eureka后,直接就是/前缀/服务id/服务端点。这样方式。
如果觉得服务前缀不爽,就要手动修改路由了,有两种方式,当然两种方式都是通过urlmapping实现的。

第一种方式:通过直接的ip地址:
网关配置

zuul:
  # 使用 prefix 添加前缀
  prefix: /zyth
  routes:
    eureka:
      path: /monitor-eureka/**
      url: http://${eureka.instance.hostname}:2000/

如上,是对eureka的配置,这个名字随便取
下面有paht,这个无论哪种配置都是一样的,就是使用path路径映射
然后,就是对应这个路径下的访问url了。
下面url针对集群,是可以配置多个的,用’,’隔开就行。
另外,以上就是,配置访问对eurake的配置逻辑。
第二种方式:通过配置服务id,就是注册到eureka上的服务名进行访问:

prefix: /zyth
  routes:
    oneservice:
      path: /monitor-eureka/**
      serviceId: serviceName

这种方式中serviceId就是eureka上,可以按照动态列表进行获取
另外对第一种方式的补充说明:
如果没有经过Eureka服务器,那自然就得不到Ribbon的负载均衡功能了。针对这个问题,Zuul已经帮开发者想到了解决方案,在这种情况下开发者只需要禁用Ribbon与Eureka的自动集成设置,采用手工设置方式开启即可,配置如下:

zuul:
	routes:
		myroutes1:
			path: /mypath/**
			serviceId: myserverId
myserverId:
	ribbon:
	listOfServers: localhost:8080, localhost:8081

ribbon:
	eureka:
	enabled: false

默认路由和安全

默认路由就是zuul啥都不配的情况下,默认就是微服务名称转向对应的微服务即:
[servicename]:[servicename]
这是一个巨坑,很多时候,我们要显示配置,微服务内部服务是不暴露出来的。这个需要手动配置关闭:

zuul:
  ignored-services: '*'
  ignoredPatterns: /**/channel/pay/**,/**/channel/transfer/**

第二个配置项,和安全相关,就是直接屏蔽掉某些不该有的pattern

跨域

跨域实际就是在响应的时候统一加上跨域头部。
实际代码可以是在setting中加入如下:

@Component
public class CorsFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
       /* String curOrigin = request.getHeader("Origin");
        System.out.println("###跨域过滤器->当前访问来源->"+curOrigin+"###");   */
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "*");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        chain.doFilter(req, res);
    }
    @Override
    public void init(FilterConfig filterConfig) {}

    @Override
    public void destroy() {}
}

说明,这个应该可以统一用网关的拦截器做的,还没有验证过,后面可以验证一下。

拦截

Zuul的拦截器,很多门道,基础就是继承一个类,实现拦截:ZuulFilter

继承方法介绍

public String filterType() 这个函数返回拦截器类型,可以之前拦截,可以之后拦截,主要看返回的是什么类型

public int filterOrder() 这个函数是返回拦截器的执行顺序,可见是可以支持多个拦截器的
public boolean shouldFilter() 这个函数是返回是否要执行本拦截

public Object run() throws ZuulException 真正的拦截器逻辑代码

设置注解

这个要只要继承就可以使用,但是如果要加入到spring的注入中,就要增加要给注解,简单的就是用:

@Component

Filter间传递值

RequestContext ctx = RequestContext.getCurrentContext();

获取基本的servlet请求对象

直接看代码吧

RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String url = request.getRequestURI();
log.info(String.format("LoginCheck(%s)->开始校验",url));

如何拦截

当该请求非法,就要拦截,拦截是在run函数里面执行的。Run函数返回null就行,无论成功还是失败。
但真正的拦截就要靠如下代码:

//登录验证失败了
ctx.set(GWConst.FILTER_RESULT_NAME_LASTRESULT,false);
ctx.setSendZuulResponse(false);
ctx.set("sendForwardFilter.ran", true);
ctx.setResponseBody(JSON.toJSONString(GWConst.LOGINCHECK_FAIL_NOLOGIN));
log.info(String.format("LoginCheck(%s)->token(%s),rbacuui(%s)校验失败",url,token,rbacUserUUID));
return null;

ctx.setSendZuulResponse(false);表示要拦截,不转向到后面微服务代码了。所以反之如果成功的话这里要设置成true
ctx.set(“sendForwardFilter.ran”, true);设置到ctx上面,标识已经拦截了,其他filter看到这个标记可以不拦截,当然这个业务自己实现的。
ctx.set(GWConst.FILTER_RESULT_NAME_LASTRESULT,false);这个就比较有门道了,这里设置了一个失败标识。为什么呢?原因是,第一个setSendZuulResponse设置成false后,是不会分发请求了,但是后续的其他filter会继续执行的。所以这里设置了一个我们自己定义的一个特殊标识,其他filter中,可以在shouldFilter山上中取这个标识并判断是否需要继续filter了
ctx.setResponseBody就是设置返回的结果了。

增加头部

一般我们的业务逻辑就是判断用户是否可以登录,如果可以登录,我们就会放置一些信息给后面的服务器,给后面服务器信息,最好就是在head上增加内容了。

//其他情况下是成功的,需要进行转换
ctx.addZuulRequestHeader(GWConst.FILTER_RESULT_NAME_HEAD_TOKEN, token);
ctx.addZuulRequestHeader(GWConst.FILTER_RESULT_NAME_HEAD_RBACUSERUUID, rbacUserUUID);

修改请求参数

参数拦截后,可能要根据拦截对象将整个请求参数修改掉。
这里分两种情况:get请求和post请求。如果参数是写在url上的get请求,直接用ctx的方法即可。
否则要重写整个requestbody。

private void resetParameter(RequestContext ctx, Map<String, List<String>> paramList) {
        String method = ctx.getRequest().getMethod();
        if ("get".equalsIgnoreCase(method)) {
            ctx.setRequestQueryParams(paramList);
        }
        else if("POST".equalsIgnoreCase(method)){
            String charSet = ctx.getRequest().getCharacterEncoding();
            try{
                StringBuilder postParamString = new StringBuilder();
                boolean isFirst = true;
                for (String name : paramList.keySet()) {
                    for (String value : paramList.get(name)) {
                        if (isFirst) {
                            isFirst = false;
                        }
                        else {
                            postParamString.append('&');
                        }
                        postParamString.append(name).append('=').append(URLEncoder.encode(value, charSet));
                    }
                }
                byte[] paramBytes = postParamString.toString().getBytes(charSet);
                ctx.setRequest(new HttpServletRequestWrapper(ctx.getRequest()){
                    @Override
                    public ServletInputStream getInputStream() throws IOException{
                        return new ServletInputStreamWrapper(paramBytes);
                    }

                    @Override
                    public int getContentLength(){
                        return paramBytes.length;
                    }

                    @Override
                    public long getContentLengthLong(){
                        return paramBytes.length;
                    }
                });
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }

返回页面的乱码问题

在yml的配置文件中增加:

spring:
  http:
    encoding:
      charset: UTF-8
      enabled: true
      force: true

或者每一个下发的时候,增加:

ctx.getResponse().setCharacterEncoding("UTF-8");

参数拦截样例代码

package com.qfkj.setting;

import com.alibaba.fastjson.JSON;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.qfkj.base.constants.GWConst;
import com.qfkj.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@Component
@Slf4j
public class LoginCheck extends ZuulFilter {
    @Value("service.base.copyrightEn")
    private String copyrightEn;

    @Value("service.base.profileActive")
    private String profileActive;

    @Value("${service.mode.check}")
    private boolean isCheck;

    @Value("${service.mode.defalutToken}")
    private String defalutToken;

    @Value("${service.mode.defalutRbacUserUUID}")
    private String defalutRbacUserUUID;

    @Value("${service.sigprefix}")
    private String sigprefix;

    @Value("${service.cookiesName}")
    private String cookiesName;

    @Value("${service.tokenRedisKey}")
    private String tokenRedisKey;

    @Autowired
    private RedisUtil redisUtil;


    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String url = request.getRequestURI();

        if (url.indexOf("swagger") > 0) {
            log.info(String.format("shouldFilter(%s)->不用校验",url));
            return false;
        }
        if (url.indexOf("webjars") > 0) {
            log.info(String.format("shouldFilter(%s)->不用校验",url));
            return false;
        }
        if (url.indexOf("/csrf") > 0) {
            log.info(String.format("shouldFilter(%s)->不用校验",url));
            return false;
        }
        if (url.indexOf("/api-docs") > 0) {
            log.info(String.format("shouldFilter(%s)->不用校验",url));
            return false;
        }
        if (url.lastIndexOf("/") == url.length()-1) {
            log.info(String.format("shouldFilter(%s)->不用校验",url));
            return false;
        }
        log.info(String.format("shouldFilter(%s)->开始校验",url));
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String url = request.getRequestURI();
        log.info(String.format("LoginCheck(%s)->开始校验",url));
        //判断当前的系统模式,是否是全场模式
        String token = this.defalutToken;
        String rbacUserUUID = this.defalutRbacUserUUID;
        if (this.isCheck) {
            //获取cookies
            String cookiesname = this.sigprefix + '-' + this.profileActive + '-' + this.cookiesName;
            Cookie[] cookies = request.getCookies();
            if (null != cookies && cookies.length > 0) {
                for (Cookie cookie : cookies){
                    if (cookiesname.equals(cookie.getName())) {
                        token = cookie.getValue();
                    }
                }
            }

            if (token!= null && !this.defalutToken.equals(token)) {
                //从redis中获取token
                String key = this.sigprefix + '-' + this.profileActive + '-' + this.tokenRedisKey + token;
                rbacUserUUID = (String) this.redisUtil.get(key);
            }
            log.info(String.format("LoginCheck(%s)->token(%s),rbacuui(%s)",url,token,rbacUserUUID));
            if (token == null || this.defalutToken.equals(token) || defalutRbacUserUUID == null) {
                //登录验证失败了
                ctx.set(GWConst.FILTER_RESULT_NAME_LASTRESULT,false);
                ctx.setSendZuulResponse(false);
                ctx.set("sendForwardFilter.ran", true);
                ctx.setResponseBody(JSON.toJSONString(GWConst.LOGINCHECK_FAIL_NOLOGIN));
                log.info(String.format("LoginCheck(%s)->token(%s),rbacuui(%s)校验失败",url,token,rbacUserUUID));
                return null;
            }
        }
        //其他情况下是成功的,需要进行转换
        ctx.addZuulRequestHeader(GWConst.FILTER_RESULT_NAME_HEAD_TOKEN, token);
        ctx.addZuulRequestHeader(GWConst.FILTER_RESULT_NAME_HEAD_RBACUSERUUID, rbacUserUUID);
        ctx.setSendZuulResponse(true);
        log.info(String.format("LoginCheck(%s)->token(%s),rbacuui(%s)校验成功",url,token,rbacUserUUID));
        return null;
    }
}

动态路由

这个实际上是根据某些拦截的结果,动态的转到后面不同的服务的方案。
比如,用户登录了,就转到user/login这个服务。否则就转到user/guest这个服务上。
直接看代码吧:

@Component
public class ForwardFilter extends ZuulFilter{
    private Logger logger= LoggerFactory.getLogger(ForwardFilter.class);

    @Override
    public String filterType() {
        //注意,重要,重要,要动态修改路由,这个值必须是ROUTE_TYPE
        return FilterConstants.ROUTE_TYPE;
    }
    @Override
    public int filterOrder() {
        // filter执行顺序,通过数字指定 ,优先级为0,数字越大,优先级越低
        return 0;
    }
    @Override
    public boolean shouldFilter() {
        // 是否执行该过滤器,此处为true,说明需要过滤
        return true;
    }
    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        //获取请求的URI  //测试访问:http://localhost:6001/testforward/hello?name=zs&token=1
        String url=request.getRequestURI();//
        if(url.indexOf("testforward")>-1){
            try {
                //[1]:设置RouteHost::说明Host我没试过,因为指定了host相当于违背了微服务了,负载均衡等要自己手动处理,一般我不这么干
                URI uri1=new URI("http://127.0.0.1:8001/");
                ctx.setRouteHost(uri1.toURL());
				//[2]:设置URI
				url=url.substring(url.indexOf("testforward")+12,url.length());
				ctx.put(FilterConstants.REQUEST_URI_KEY,url);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

这里有一个很重要的代码:

public String filterType() {
        //注意,重要,重要,要动态修改路由,这个值必须是ROUTE_TYPE
        return FilterConstants.ROUTE_TYPE;
    }

config手动刷新路由配置

这个部分参见Spring-Cloud-Config部分说明。
这里注意的是,ruul的路由配置,可能自己一行实现了@ @RefreshScope注解,路由刷新,用curl -X POST http://localhost:4001/actuator/refresh访问一次即可

Zuul中使用Controller

一般而言,zuul中大部分都是路由配置服务,但也可以直接编写普通的Controller进行访问。Controller编写后,起Mapping可以直接生效,无需在进行路由映射配置。
值得注意的时候,路由映射的优先级会大于Controller,换句话说,通过路由配置,可以直接将其结果转换拦截掉。

Zuul中的Forward

在zuul中可以配置forward的

### 网关配置
zuul:
  # 路由信息配置
  routes:
    demo-local:
      # 访问的路径,此处要以 '/do/' 开头
      path: /local1/**
      # 访问的 url,forward:向本地转发
      url: forward:/local2

而controller如下:

/**
 * @Author:大漠知秋
 * @Description:网关本地的 Controller
 * @CreateDate:1:33 PM 2018/10/30
 */
@RestController
@RequestMapping(
        value = "/local2",
        produces = MediaType.APPLICATION_JSON_UTF8_VALUE
)
public class LocalController {

    @RequestMapping(value = "/testOne")
    public String testOne() {

        return "testOne";

    }

}

如上,正常访问/local2/testOne来访问服务。
但有上面的拦截和forward,那么,就可以通过/local1/testOne来访问

你可能感兴趣的:(java,spring,cloud,java,uml)