- 客户端维护大量的ip和port信息,直接访问指定服务
- 认证和授权操作,需要在每一个模块中都添加认证和授权的操作
- 项目的迭代,服务要拆分,服务要合并,需要客户端进行大量的变化
- 统一的把安全性校验都放在Zuul中
1 Zuul的快速入门
创建Maven项目,修改为SpringBoot
导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
添加一个注解
@EnableEurekaClient
@EnableZuulProxy
编写配置文件
# 指定Eureka服务地址
eureka:
client:
service-url:
defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka
#指定服务的名称
spring:
application:
name: ZUUL
server:
port: 80
2 Zuul的监控界面
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
访问地址:
http://localhost/actuator/routes (http://ip:port/actuator/routes)
编写配置文件
# 查看zuul的监控界面(开发时,配置为*,上线,不要配置)
management:
endpoints:
web:
exposure:
include: "*"
3 忽略服务配置
# zuul的配置
zuul:
# 基于服务名忽略服务,无法查看 ,如果要忽略全部的服务 "*",默认配置的全部路径都会被忽略掉(自定义服务的配置,无法忽略的)
ignored-services: eureka
# 监控界面依然可以查看,在访问的时候,404
ignored-patterns: /**/search/**
4 自定义服务配置
# zuul的配置
zuul:
# 指定自定义服务(方式一 , key(服务名):value(路径))
# routes:
# search: /ss/**
# customer: /cc/**
# 指定自定义服务(方式二)
routes:
kehu: # 自定义名称
path: /ccc/** # 映射的路径
serviceId: customer # 服务名称
5 灰度发布
添加一个配置类
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?<name>^.+)-(?<version>v.+$)",
"${version}/${name}");
}
准备一个服务,提供2个版本
version: v1
#指定服务的名称
spring:
application:
name: CUSTOMER-${version}
-Dversion=v2 -Dserver.port=9099
修改Zuul的配置
# zuul的配置
zuul:
# 基于服务名忽略服务,无法查看 , 如果需要用到-v的方式,一定要忽略掉
# ignored-services: "*"
6 Zuul的过滤器执行流程
客户端请求发送到Zuul服务上,首先通过PreFilter链,如果正常放行,会吧请求再次转发给RoutingFilter,请求转发到一个指定的服务,在指定的服务响应一个结果之后,再次走一个PostFilter的过滤器链,最终再将响应信息交给客户端。
Zuul过滤器入门
创建POJO类,继承ZuulFilter抽象类
@Component
public class TestZuulFilter extends ZuulFilter {}
指定当前过滤器的类型
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
指定过滤器的执行顺序
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
}
配置是否启用
@Override
public boolean shouldFilter() {
// 开启当前过滤器
return true;
}
指定过滤器中的具体业务代码
@Override
public Object run() throws ZuulException {
System.out.println("prefix过滤器执行~~~");
return null;
}
PreFilter实现token校验 (作业)
准备访问路径,请求参数传递token
http://localhost/v2/customer/version?token=123
创建AuthenticationFilter
@Component
public class AuthenticationFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 2;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
//..
}
}
在run方法中编写具体的业务逻辑代码
@Override
public Object run() throws ZuulException {
//1. 获取Request对象
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//2. 获取token参数
String token = request.getParameter("token");
//3. 对比token
if(token == null || !"123".equalsIgnoreCase(token)) {
//4. token校验失败,直接响应数据
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
return null;
}
Zuul的降级
创建POJO类,实现接口FallbackProvider
@Component
public class ZuulFallBack implements FallbackProvider {}
重写两个方法
@Override
public String getRoute() {
return "*"; // 代表指定全部出现问题的服务,都走这个降级方法
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
System.out.println("降级的服务:" + route);
cause.printStackTrace();
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
// 指定具体的HttpStatus
return HttpStatus.INTERNAL_SERVER_ERROR;
}
@Override
public int getRawStatusCode() throws IOException {
// 返回的状态码
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
@Override
public String getStatusText() throws IOException {
// 指定错误信息
return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
// 给用户响应的信息
String msg = "当前服务:" + route + "出现问题!!!";
return new ByteArrayInputStream(msg.getBytes());
}
@Override
public HttpHeaders getHeaders() {
// 指定响应头信息
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
Zuul动态路由(作业)
创建一个过滤器
// 执行顺序最好放在Pre过滤器的最后面
在run方法中编写业务逻辑
@Override
public Object run() throws ZuulException {
//1. 获取Request对象
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
//2. 获取参数,redisKey
String redisKey = request.getParameter("redisKey");
//3. 直接判断
if(redisKey != null && redisKey.equalsIgnoreCase("customer")){
// http://localhost:8080/customer
context.put(FilterConstants.SERVICE_ID_KEY,"customer-v1");
context.put(FilterConstants.REQUEST_URI_KEY,"/customer");
}else if(redisKey != null && redisKey.equalsIgnoreCase("search")){
// http://localhost:8081/search/1
context.put(FilterConstants.SERVICE_ID_KEY,"search");
context.put(FilterConstants.REQUEST_URI_KEY,"/search/1");
}
return null;
}