Spring Cloud Zuul主要的功能是:路由跳转以及路由过滤,本文主要讲解了这两方面的内容。在实际项目中,zuul中可能也会有相对复杂的逻辑,通常在zuul前面还有一个nginx反向代理,前端直接访问nginx,让nginx给我们代理到网关服务,网关服务再路由到具体的服务提供者上。zuul默认集成了ribbon实现了负载均衡功能。
a. zuul-eureka-server,端口1111
b.zuul-service:服务提供者,端口2222
c.zuul-api-gateway:网关服务,端口3333
本文不对eureka服务注册中心的搭建做过多讲解,主要讲解api-gateway网关服务的搭建过程。
zuuk-service也只是一个比较简答的服务提供者,主要是要暴露一个接口给外部进行访问。主要代码如下:
pom.xml:
4.0.0
com.springcloud.wsh
springcloud_zuul_service
0.0.1-SNAPSHOT
jar
springcloud_zuul_service
服务提供者
org.springframework.boot
spring-boot-starter-parent
1.5.2.RELEASE
UTF-8
UTF-8
1.8
Camden.SR6
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
启动类:
@SpringBootApplication
@EnableDiscoveryClient
public class SpringcloudZuulServiceApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudZuulServiceApplication.class, args);
}
}
ZuulServiceController:
/**
* @Title: ZuulServiceController
* @ProjectName springcloud_zuul_api_gateway
* @Description: 对外提供接口服务
* @Author WeiShiHuai
* @Date 2018/9/14 14:16
*/
@RestController
public class ZuulServiceController {
private static Logger logger = LoggerFactory.getLogger(ZuulServiceController.class);
@Value("${server.port}")
private String port;
@RequestMapping("/getInfo/{name}")
public String getInfo(@PathVariable("name") String name) {
String info = "hello " + name + " ,i am from zuul service,port = " + port;
logger.info(info);
return info;
}
}
首先引入zuul的依赖,具体pom.xml如下:
4.0.0
com.springcloud.wsh
springcloud_api_gateway
0.0.1-SNAPSHOT
jar
springcloud_api_gateway
Spring Cloud Zuul服务网关
org.springframework.boot
spring-boot-starter-parent
1.5.2.RELEASE
UTF-8
UTF-8
1.8
Camden.SR6
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-zuul
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
@EnableZuulProxy注解主要是用于开启zuul路由转发以及路由过滤功能。
/**
* @Description: api网关启动类
* @Author: WeiShiHuai
* @Date: 2018/9/14 14:22
* Zuul的主要功能是路由转发和过滤器
* 通常情况下,可以在网关服务进行权限控制等,将具体业务逻辑与权限控制分开,有利于解耦; Zuul也可以实现对服务的负载均衡
* Zuul主要作用是请求转发,和过滤,请求转发是做了负载均衡。Zuul也需要做一次集群,因为 Zuul是网关,可能需要做很复杂的逻辑,比如查数据库,还有静态资源,在最外一层需要再一个zuul或者nginx去路由。
* 通常情况下由nginx做第一层比较好,第二层的一些例如权限等校验交由zuul处理
* 路由配置方式: a、path-url方式 b、path-serviceId方式,推荐使用path-serviceId方式,方便后期网关服务维护
*/
@SpringBootApplication
@EnableDiscoveryClient
//@EnableZuulProxy注解用于开启Zuul路由功能(反向代理)
@EnableZuulProxy
public class SpringcloudApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudApiGatewayApplication.class, args);
}
}
zuul的路由规则主要有两种配置方式,path-url以及path-serviceId两种方式,但是path-url不利于维护,要具体制定某个服务的url,其实在微服务中,每个服务都注册到eureka服务注册中心上,这个时候其实可以通过serviceId直接找到具体指向的服务。推荐使用path-serviceId方式配置。
server:
port: 3333
spring:
application:
name: api-gateway
eureka:
client:
service-url:
defaultZone: http://localhost:1111/eureka/
#服务路由:推荐使用serviceId方式配置路由,方便后期维护
zuul:
routes:
zuul-service-url:
#通过path/url方式配置路由: 以/zuul-service-url开头的请求都转发到zuul-service
path: /zuul-service-url/**
url: http://localhost:2222/
#通过path/serviceId方式配置路由: 以/zuul-service-serviceId开头的请求都转发到zuul-service
zuul-service-serviceId:
path: /zuul-service-serviceId/**
#对应注册到Eureka中的serviceId
serviceId: zuul-service
#通过配置以上路由,前台我们访问地址:
# http://localhost:3333/zuul-service-url/getInfo/weixiaohuai
# >>>>>路由到(实际请求的地址)>>>>>
# http://localhost:2222/getInfo/weixiaohuai
# http://localhost:3333/zuul-service-serviceId/getInfo/weixiaohuai
# >>>>>路由到(实际请求的地址)>>>>>
# http://localhost:2222/getInfo/weixiaohuai
#通过配置以上路由,前台我们访问地址:
a. path-url方式:
# http://localhost:3333/zuul-service-url/getInfo/weixiaohuai
# >>>>>路由到(实际请求的地址)>>>>>
# http://localhost:2222/getInfo/weixiaohuai
b. path-serviceId方式:
# http://localhost:3333/zuul-service-serviceId/getInfo/weixiaohuai
# >>>>>路由到(实际请求的地址)>>>>>
# http://localhost:2222/getInfo/weixiaohuai
分别启动eureka-servier、zuul-service、zuul-api-gateway
我们通过浏览器访问
http://localhost:3333/zuul-service-url/getInfo/weixiaohuai
可以看到,网关已经帮我们路由到zuul-service服务,成功返回数据,这是path-url方式。
接着我们访问
http://localhost:3333/zuul-service-serviceId/getInfo/weixiaohuai
可以看到,网关同样帮我们路由到了zuul-service服务,成功返回数据,这是path-serviceId方式。
以上就完成了zuul路由跳转的功能,接下来我们谈谈zuul路由过滤的功能,在实际项目中,通常需要进行权限校验的功能,这里模拟验证url中是否传username以及password参数并且校验是否正确。zuul路由过滤是通过过滤器实现的,通常继承ZuulFilter类,并且实现其中的方法即可。
ZuulFilter中方法的介绍:
a. filterType:指定过滤器的类型,可选择的值有:
pre:在路由之前执行
routing:在路由的时候执行
post:在路由完成之后执行
error:路由发生错误时执行
b. filterOrder:指定过滤器的执行顺序,数字越大,优先级越低
c. shouldFilter:是否需要过滤,return true表示需要执行该过滤器
d. run:过滤器的具体实现逻辑
下面我们创建一个校验用户名的AccessUsernameZuulFilter:
/**
* @Title: AccessUsernameZuulFilter
* @ProjectName springcloud_zuul_api_gateway
* @Description: 校验用户名的Zuul过滤器
* @Author WeiShiHuai
* @Date 2018/9/14 15:02
*/
@Component
public class AccessUsernameZuulFilter extends ZuulFilter {
private static Logger logger = LoggerFactory.getLogger(AccessUsernameZuulFilter.class);
private static final String USER_NAME = "weixiaohuai";
/**
* 过滤器类型
* pre:在路由之前执行 routing:在路由的时候执行 post:在路由完成之后执行 error:路由发生错误时执行
*
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤器的执行顺序
* 数字越大,优先级越低
*
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 是否需要过滤,true表示过滤,false表示不过滤
*
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤器具体校验逻辑,如权限校验等等
* 实际项目中可以进行数据库查询权限数据库等等
*
* @return
*/
@Override
public Object run() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//获取请求url中的username
String username = request.getParameter("username");
//说明用户名不为空且正确
if (null != username && USER_NAME.equals(username)) {
//表示让Zuul进行路由跳转
requestContext.setSendZuulResponse(true);
//设置响应码
requestContext.setResponseStatusCode(200);
logger.info("welcome,the username is valid!!");
//记录这个过滤器的状态
requestContext.set("isSuccess", true);
return null;
} else { //用户名不正确
//表示让Zuul过滤这个请求,不进行路由跳转
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(401);
//记录这个过滤器的状态
requestContext.set("isSuccess", false);
logger.info("sorry,the username is not valid, please try again!!");
requestContext.setResponseBody("sorry,the username is not valid, please try again!!");
return null;
}
}
}
注意:
//表示让Zuul进行路由跳转
requestContext.setSendZuulResponse(true);
//表示让Zuul过滤这个请求,不进行路由跳转
requestContext.setSendZuulResponse(false);
//设置响应码
requestContext.setResponseStatusCode(200);
跟校验用户名的类似,注意在shouldFilter()方法中拿到全局保存的isSuccess变量,由于标识该过滤器是否需要被执行,如果用户名都没校验通过,显然不需要执行密码的校验。
/**
* @Title: AccessPasswordZuulFilter
* @ProjectName springcloud_zuul_api_gateway
* @Description: 校验密码的Zuul过滤器
* @Author WeiShiHuai
* @Date 2018/9/14 15:37
*/
@Component
public class AccessPasswordZuulFilter extends ZuulFilter {
private static Logger logger = LoggerFactory.getLogger(AccessPasswordZuulFilter.class);
private static final String PASSWORD = "123456";
/**
* 过滤器类型
* pre:在路由之前执行 routing:在路由的时候执行 post:在路由完成之后执行 error:路由发生错误时执行
*
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤器的执行顺序
* 数字越大,优先级越低
*
* @return
*/
@Override
public int filterOrder() {
return 1;
}
/**
* 是否需要过滤,true表示过滤,false表示不过滤
*
* @return
*/
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
//获取上一个过滤器保存的过滤器状态,如果返回false,则说明上一个过滤器没有成功,则无需执行后面的过滤器,直接返回结果
return (boolean) requestContext.get("isSuccess");
}
/**
* 过滤器具体校验逻辑,如权限校验等等
* 实际项目中可以进行数据库查询权限数据库等等
*
* @return
*/
@Override
public Object run() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//获取请求url中的password
String password = request.getParameter("password");
//说明密码不为空且正确
if (null != password && PASSWORD.equals(password)) {
//表示让Zuul进行路由跳转
requestContext.setSendZuulResponse(true);
//设置响应码
requestContext.setResponseStatusCode(200);
//记录这个过滤器的状态
requestContext.set("isSuccess", true);
logger.info("welcome,the password is valid!!");
return null;
} else { //用户名不正确
//表示让Zuul过滤这个请求,不进行路由跳转
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(401);
//记录这个过滤器的状态
requestContext.set("isSuccess", false);
logger.info("sorry,the password is not valid, please try again!!");
requestContext.setResponseBody("sorry,the password is not valid, please try again!!");
return null;
}
}
}
@SpringBootApplication
@EnableDiscoveryClient
//@EnableZuulProxy注解用于开启Zuul路由功能(反向代理)
@EnableZuulProxy
public class SpringcloudApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudApiGatewayApplication.class, args);
}
@Bean
AccessUsernameZuulFilter accessUsernameZuulFilter() {
return new AccessUsernameZuulFilter();
}
@Bean
AccessPasswordZuulFilter accessPasswordZuulFilter() {
return new AccessPasswordZuulFilter();
}
}
分别启动eureka-server、zuul-service、zuul-api-gateway
我们浏览器访问http://localhost:3333/zuul-service-url/getInfo/weixiaohuai或者http://localhost:3333/zuul-service-serviceId/getInfo/weixiaohuai,如下图
可以看到,如果url中没有传入username或者password参数的话,返回我们自定义的错误信息
a. 如果我们访问http://localhost:3333/zuul-service-url/getInfo/weixiaohuai?username=weixiaohuai,这时候校验用户名的过滤器已经通过,被校验密码的过滤器拦截了,提示密码不合法
b. 如果我们访问http://localhost:3333/zuul-service-url/getInfo/weixiaohuai?password=123456,同样确实username参数,如下图返回用户名不合法的错误
c. 如果我们访问http://localhost:3333/zuul-service-url/getInfo/weixiaohuai?username=weixiaohuai&password=123456,这时候路由就成功跳转到zuul-service中
至此,zuul路由过滤功能已经实现完成。在实际项目中,需要结合权限验证框架进行实现,这里只是演示zuul过滤器的一些用法。本文是作者在学习过程中的一些总结,文章仅供参考,希望大家一起学习,共同进步。