Zuul的主要功能是路由转发和过滤器。路由是微服务体系结构的一个组成部分。例如,/可以映射到您的Web应用程序,/api/users映射到用户服务,并将/api/shop映射到商店服务。Zuul是Netflix的基于JVM的路由器和服务器端负载均衡器。
感兴趣的朋友可以访问: Spring Cloud中文网 里面有详细的介绍。
后面的所有文章基本上都在前面的几篇博文中进行修改,所以,不明白的朋友,可以查看之前的文章。
网关作为一个独立的服务,所以,要创建一个新的Module,命名为api-gateway,然后需要导入相关的约束。
api-gateway的pom文件:
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.root.projectgroupId>
<artifactId>api-gatewayartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>api-gatewayname>
<packaging>jarpackaging>
<description>Demo project for Spring Bootdescription>
<parent>
<groupId>com.root.projectgroupId>
<artifactId>springcloud-projectartifactId>
<version>1.0-SNAPSHOTversion>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-zuulartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
然后在启动类上加入@EnableZuulProxy注解,表示开启Zuul功能
@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
接着,就是将它往注册中心注册,需要在yml文件中增加相应的配置。
spring:
application:
name: api-gateway
server:
port: 9999
#指定注册中心
eureka:
client:
service-url:
defaultzone: http://localhost:8761/eureka/
然后,网关就添加完成了,是不是很简单呢?接下来,我们启动所有服务,进行测试。
在启动服务的时候,出现了问题,原因是:Spring Cloud和Spring Boot的版本不一致。博主使用的Spring Boot版本是2.1.1.RELEASE,Spring Cloud是Finchley.RELEASE,而这个版本对应的是Spring Boot2.0.x。所以我就将Spring Cloud换成了Greenwich.RELEASE版本,成功的解决了问题。如果出现上面问题,可以去官网看它们之间版本是如何对应的。
接着重新启动服务,一切正常,通过网关来访问服务。我这里的网关端口号是:9999。地址是:http://localhost:9999/user-service/v1.0/user/1 网关端口号+服务名称+接口地址。
可以看到,这个和调用自身服务(http://localhost:8802/v1.0/user/1),返回的结果是一样的。但是,这样有一个弊端,每次访问,都需要加上服务名称,服务庞大的时候,这是非常繁琐的。我们想让/api/user映射到用户服务,并将/api/order映射到订单服务。这该怎么实现呢?
接下来就要用到Zuul的路由了,需要在yml配置文件中给各个服务配置相应的路由策略。
zuul:
routes:
order-service: /api/order/**
admin-service: /api/admin/**
user-service: /api/user/**
# #忽略下面正则规则的访问
ignored-patterns: /*-service/**
#
# #处理http请求头为空的问题,默认将"Cookie", "Set-Cookie", "Authorization"过滤,所以获取不到Cookie
sensitive-headers:
新增上面的配置,zuul.routes配置的是路由策略,就是把order-service映射成以/api/order/开头,**是匹配所有。
需要注意的是,不能把所有服务都映射成相同的地址。因为routes对应的源码是一个Map集合,Map中键相同的,越底下的值会把上面的值覆盖。
ignored-patterns:是用正则的方式忽略不必要的请求。既然,已经通过映射给各个服务配置了相应的路由,就把原来的服务名称访问的方式忽略掉。至于sensitive-headers,后面再讲。
配置完成,就可以拿映射后的路径来访问了,重启服务,访问地址:http://localhost:9999/api/order/v1.0/order?userId=3
得到结果是一样的。
现在来说一下sensitive-headers这个配置,在源码中,它是一个LinkedHashSet集合,意思是将"Cookie", “Set-Cookie”, "Authorization"过滤掉。如果说,你在Cookie里存放了一些信息,不去配置这个,使其为空,就不能拿到Cookie中的值。下面通过代码来证明吧。
先看没有配置之前的响应,将sensitive-headers这个配置注释掉,然后对用户服务进行修改。
private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class);
@GetMapping(value = "/user")
public Result findAll(HttpServletRequest request) {
String token = request.getHeader("token");
String cookie = request.getHeader("cookie");
LOGGER.info("token:{}",token);
LOGGER.info("cookie:{}",cookie);
Map<Long, User> map = userService.findAll();
return ResultBean.success("查询全部用户", map);
}
通过打日志的方式,给大家展示,启动服务。用postman工具进行测试。
在Headers中输入token和cookie,传递到后台,让后台接收。
通过日志,可以看到,明明Headers中有设置cookie,但是后台接收的却显示为null,token是有值的。
然后,把sensitive-headers的注释去掉,重启服务。
结果,不出意外,成功的打印了token和cookie的值。路由基本上就讲到这里,接下给大家讲Zuul的过滤。
假设案例:管理员需要登录之后,将鉴权的token传递到后台,才能访问用户服务。反之,则失败。
按照以前的实现方法,是将登陆后的信息放入session中,然后再对session中的信息进行判断。但是,如果服务过多,难道所有服务都要去判断一遍?此时,Zuul的过滤器就体现了一种“切面”的思想。直接在网关层,对服务进行权限的判断。
要想实现过滤的功能,就要自己写过滤器,来继承ZuulFilter,和以前在Servlet中一样,当然Spring Boot中也是有过滤器的。下面,就写一个处理登录请求的过滤器。
LoginFilter
package com.root.project.apigateway.filter;
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.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* @ClassName: LoginFilter
* @Author: 清风一阵吹我心
* @Description: TODO 新建类实现ZuulFilter 。将此类注入spring中
* @Date: 2019/2/13 10:35
* @Version 1.0
**/
@Component
public class LoginFilter extends ZuulFilter {
/**
* FilterConstants这个类中定义了type的常量。
* pre:前置
* post: 后置
* error: 发生错误
* route: 路由时
*
* @return
*/
@Override
public String filterType() {
return PRE_TYPE;
}
/**
* filterOrder 过滤器执行顺序,越小越先执行
*
* @return
*/
@Override
public int filterOrder() {
return 4;
}
/**
* false: 不生效
* true: 生效
* 可以通过一定逻辑判断过滤器是否生效
*
* @return
*/
@Override
public boolean shouldFilter() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String admin = "/api/admin/v1.0/admin";
/* if (admin.equalsIgnoreCase(request.getRequestURI())){
return true;
}
return false;*/
return admin.equalsIgnoreCase(request.getRequestURI());
}
/**
* 处理逻辑
*
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String token = request.getHeader("token");
//HttpStatus枚举http状态码
if (StringUtils.isBlank(token)) {
context.setSendZuulResponse(false);
context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
HttpServletResponse response = context.getResponse();
try {
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("接口调用失败,token为空");
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
基本上,所有的方法上都写明了注释,大家在进行测试的时候,可以进入每一个方法的源码中进行查看。token为空,就不让服务继续调用了,同时返回一个指定的状态码,并在页面中返回“接口调用失败,token为空”的字符串,增加用户体验。HttpStatus这个工具类,以枚举的方式,封装了各种Http状态码,大家可以去看一下它的使用方法。
顺便给大家拓展一下知识。
if (admin.equalsIgnoreCase(request.getRequestURI())){
return true;
}
return false;
以前大家写if的时候,是不是成立就返回true,失败就返回false。为了可读性,其实可以用下面的方式,替换上面的结构。
return admin.equalsIgnoreCase(request.getRequestURI());
可以看到,一句话就解决了问题。当然,这是给不知道的朋友扩展的知识,知道的,就当复习一遍了。
过滤器写完,就该测试是否可行了。然后重启服务。为了方便测试,还是使用postman工具。
可以看到,这是没有token返回的结果,过滤器阻止了服务的调用。接下来,将token传递到后台,再看结果。
返回了正确的响应。这就是Zuul的过滤功能。讲到这里,Zuul的基本功能就完成了,更深层次的东西,还需要大家自己去挖掘。努力吧!各位志同道合的猿友。希望你们在码代码的路上,越走越远。
爱生活,爱清风,更爱自己。