1.前置准备
之前教程中的项目搭建好
- [SpringCould]之实战开篇
- [SpringCould篇]之服务注册与发现Eureka服务端搭建
- [SpringCould篇]之服务生产者项目搭建
- [SpringCould篇]之服务消费者方式Feign
- [SpringCould篇]之服务消费方式Ribbon
2.创建server-zuul工程
2.1添加依赖
<parent><artifactId>spring-could-example</artifactId><groupId>com.example</groupId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>server-zuul</artifactId><description>路由网关zuul</description><dependencies><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-zuul</artifactId></dependency></dependencies>
2.2配置文件
最底部是zuul的配置
server:port: 8704#应用名称
spring:application:name: server-zuul# 注册中心配置
eureka:client:serviceUrl:# 配置服务注册中心集群时,此处可以配置多个地址(通过逗号隔开)defaultZone: http://127.0.0.1:7001/eureka/zuul:routes:api-ribbon:path: /api-ribbon/**serviceId: server-consum-ribbonapi-feign:path: /api-feign/**serviceId: server-consum-feign
配置说明
- 指定服务注册中心的地址为http://127.0.0.1:7001/eureka/;
- server-zuul服务的端口为8704,服务名为server-zuul;
- 以/api-ribbon/ 开头的请求均转发到server-consum-ribbon服务;
- 以/api-feign/开头的请求均转发到server-consum-feign服务;
2.3启动类
在其入口启动类需加上注解@EnableZuulProxy,开启zuul的功能
/*** 路由网关** @author 程序员小强*/
@EnableZuulProxy
@EnableEurekaClient
@EnableDiscoveryClient
@SpringBootApplication
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);}
}
2.4启动测试
依次启动
- server-eureka 注册中心
- server-provider 服务生产者
- server-consum-ribbon 消费者
- server-consum-feign 消费者
- server-zuul 网关zuul
测试负载均衡,生成者服务启动了多个
2.4.1api-feign请求
由测试结果可得负载均衡生效了并且 api-feign类的请求都转发到了server-consum-feign服务
2.4.2api-ribbon请求
由测试结果可得负载均衡生效了并且 api-ribbon类的请求都转发到了server-consum-ribbon服务
3.服务过滤
zuul除了路由功能外,并且还能过滤,比如日志打印,token校验等
3.1过滤器类型
Zuul中有以下几种典型的过滤器类型。
- pre:前置
- routing:执行中
- post:后置
- error:执行错误时
3.2自定义过滤器
- 自定义过滤器继承ZuulFilter。重写以下4个方法
- filterType 过滤器类型,可选值有 pre、route、post、error。
- filterOrder 过滤器的执行顺序,数值越小,优先级越高。
- shouldFilter 是否执行该过滤器
- run 执行自己的业务逻辑-示例以下自定义校验token过滤器
/*** 定义 zuul token参数校验-过滤器** @author 程序员小强*/
@Component
public class ZuulCheckTokenFilter extends ZuulFilter {
private static final Logger log = LoggerFactory.getLogger(ZuulCheckTokenFilter.class);/*** 定义filter的类型,有pre、route、post、error四种*/@Overridepublic String filterType() {
return "pre";}/*** 定义filter的顺序,数字越小表示顺序越高,越先执行*/@Overridepublic int filterOrder() {
return 0;}/*** 可以自定义-逻辑判断,是否要过滤*/@Overridepublic boolean shouldFilter() {
return true;}@Overridepublic Object run() {
//请求上下文RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();String host = request.getRemoteHost();String method = request.getMethod();String uri = request.getRequestURI();log.info("[ Zuul CheckTokenFilter ] start >> host:{},requestMethod:{},url:{}", host, method, uri);//TODO 自定义功能 示例校验token 参数Object token = request.getParameter("token");if (!ObjectUtils.isEmpty(token)) {
// TODO 校验token...log.info("[ Zuul CheckTokenFilter ] end >> host:{},requestMethod:{},url:{},token:{}", host, method, uri, token);//校验通过-返回 null 即可,会继续执行后续操作return null;}log.error("[ Zuul CheckTokenFilter ] token is empty >> host:{},method:{},url:{}", host, method, uri);//失败返回ctx.setSendZuulResponse(false);ctx.setResponseStatusCode(401);try {
//返回信息Map<String, Object> resultMap = new LinkedHashMap<>(4);resultMap.put("code", 401);resultMap.put("messages", "token is empty");//写出异常内容ctx.getResponse().getWriter().write(JSON.toJSONString(resultMap));ctx.getResponse().setContentType("application/json; charset=utf-8");} catch (Exception e) {
log.error("[ Zuul CheckTokenFilter ] exception >> host:{},method:{},url:{}", host, method, uri, e);}return null;}
}
3.3测试过滤器
由测试结果看,过滤器生效了,未加token参数情况下,直接被拦截并且返回了自定义的异常信息,加了token参数后就继续执行调用了后续的服务。
3.4过滤器间传递数据
项目中一般会根据不同的需求创建多个过滤器,执行顺序是根据filterOrder决定的,若前一个过滤器需要传值到后一个过滤器,应该怎么做呢?
可以RequestContext 的 set 方法进行传递,RequestContext 是基于ThreadLocal实现的
RequestContext ctx = RequestContext.getCurrentContext();
ctx.set("helloworld", "hello zuul ");
后面执行的过滤器可以通过 RequestContext 的 get 方法来获取数据:
RequestContext ctx = RequestContext.getCurrentContext();
ctx.get("helloworld");
3.5过滤器中异常处理
过滤器中的异常主要发生在 run 方法中,可以用 try catch 来处理。
3.5.1方式一 try-catch
使用try-catch包裹run方法强制捕获
3.5.2方式二 异常过滤器
Zuul 提供了一个异常处理的过滤器,当过滤器在执行过程中发生异常,若没有捕获,就会进入 error 过滤器中。
可以定义一个 error 过滤器来记录异常信息,示例代码如下所示。
/*** 异常过滤器** @author 程序员小强*/
@Component
public class ErrorFilter extends ZuulFilter {
private static final Logger log = LoggerFactory.getLogger(ErrorFilter.class);@Overridepublic String filterType() {
return "error";}@Overridepublic int filterOrder() {
return 0;}@Overridepublic boolean shouldFilter() {
return true;}@Overridepublic Object run() {
log.error("[ Zuul ErrorFilter ] start ");RequestContext ctx = RequestContext.getCurrentContext();Throwable throwable = ctx.getThrowable();log.error("[ Zuul ErrorFilter ] stack ", throwable.getCause());return null;}
}
3.5.3统一异常返回值
在任意非error过滤器中加入如下代码,模拟异常
//模拟异常
int i = 1 / 0;
测试一下,结果如下图
后端的接口服务都是 REST 风格的API,返回的数据都有固定的 Json 格式。现在看来,约定的格式已经不复存在?那么怎么解决呢?
配置ErrorHandlerController
/*** 异常后统一返回 controller** @author 程序员小强*/
@RestController
public class ErrorHandlerController implements ErrorController {
private static final Logger log = LoggerFactory.getLogger(ErrorHandlerController.class);@Autowiredprivate ErrorAttributes errorAttributes;@Overridepublic String getErrorPath() {
return "/error";}@RequestMapping(value = "/error", produces = "application/json; charset=utf-8")public Map<String, Object> error(HttpServletRequest request) {
Map<String, Object> errorAttributes = getErrorAttributes(request);String message = (String) errorAttributes.get("message");//堆栈信息String trace = (String) errorAttributes.get("trace");//异常Exception exception = (Exception) request.getAttribute("javax.servlet.error.exception");Map<String, Object> resultMap = new LinkedHashMap<>();resultMap.put("code", 500);resultMap.put("messages", "网络繁忙,请稍后再试 ," + exception.getMessage());return resultMap;}private Map<String, Object> getErrorAttributes(HttpServletRequest request) {
return errorAttributes.getErrorAttributes(new ServletWebRequest(request), true);}}
添加以上代码后效果
4.源码地址
传送门
关注程序员小强公众号更多编程趣事,知识心得与您分享