当前位置: 代码迷 >> 综合 >> [SpringCould篇]之服务网关(zuul)代码实战
  详细解决方案

[SpringCould篇]之服务网关(zuul)代码实战

热度:56   发布时间:2023-12-03 19:38:15.0

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.源码地址

传送门

关注程序员小强公众号更多编程趣事,知识心得与您分享
在这里插入图片描述