up:: SpringCloud之Gateway新网关开发

说明: 学习本节前先复习以下文章。

SpringBoot电商项目购物车模块统一校验当前是否有用户登录SpringCloud之Feign携带session信息

前面开发完了Gateway网关,现在开始将Gateway网关应用到我们的SpringCloud电商项目中,将zuul升级为gateway。。。

zuul升级为gateway

说明: 我们首先知道的是,我们使用了JWT认证,所以将以前的session认证全部取消掉,改为jwt企业级认证。

我们一个个模块来看,首先看用户模块:

排除依赖

去除注解

去除配置文件

上面的操作是其它模块也要做的,跟着此模块操作即可。


查找相关session代码

接下来修改session有关代码,升级为jwt.

首先是Controller层

说明:

相关线程池的使用参考SpringCloud之线程池和Threadlocal在项目中的应用

配置过滤器与实现

先查看下两者之间关系:

package com.imooc.cloud.mall.practice.user.filter;
 
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
/**
 * 描述:     User过滤器的配置
 */
@Configuration
public class UserFilterConfig {
 
    @Bean
    public UserInfoFilter userInfoInterceptor() {
        return new UserInfoFilter();
    }
 
    @Bean(name = "userFilterConf")
    public FilterRegistrationBean userFilterConfig() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(userInfoInterceptor());
        filterRegistrationBean.addUrlPatterns("/cart/*");
        filterRegistrationBean.addUrlPatterns("/order/*");
        filterRegistrationBean.addUrlPatterns("/user/update");
        filterRegistrationBean.setName("userFilterConf");
        return filterRegistrationBean;
    }
}

先查看:

SpringBoot系列教程web篇之过滤器Filter使用指南 - 腾讯云开发者社区-腾讯云

接下来实现过滤器:

package com.imooc.cloud.mall.practice.user.filter;
 
import com.imooc.cloud.mall.practice.common.common.Constant;
import com.imooc.cloud.mall.practice.user.model.pojo.User;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
 
/**
 * 把Header里的用户信息,放到本地的ThreadLocal里
 */
@Configuration
@EnableFeignClients
public class UserInfoFilter implements Filter {
 
    public static ThreadLocal<User> userThreadLocal = new ThreadLocal();
    public User currentUser = new User();
 
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
 
    }
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //通过RequestContextHolder获取本地请求
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            return;
        }
        //获取本地线程绑定的请求对象
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        //给请求模板附加本地线程头部信息,把User信息放到ThreadLocal里
        Enumeration<String> headerNames = request.getHeaderNames();
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                Enumeration<String> values = request.getHeaders(name);
                while (values.hasMoreElements()) {
                    String value = values.nextElement();
                    if (name.equals(Constant.USER_ID)) {
                        currentUser.setId(Integer.valueOf(value));
                    }
                    if (name.equals(Constant.USER_NAME)) {
                        currentUser.setUsername(value);
                    }
                    if (name.equals(Constant.USER_ROLE)) {
                        currentUser.setRole(Integer.valueOf(value));
                    }
                    //必须保证User的3个字段都完整,否则意味着网关传递信息时出错了
                    if (currentUser.getId() != null && currentUser.getUsername() != null && currentUser.getRole() != null) {
                        userThreadLocal.set(currentUser);
                    }
                }
            }
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
 
    @Override
    public void destroy() {
 
    }
}

Filter,过滤器,属于Servlet规范,并不是Spring独有的。其作用从命名上也可以看出一二,拦截一个请求,做一些业务逻辑操作,然后可以决定请求是否可以继续往下分发,落到其他的Filter或者对应的Servlet

简单描述下一个http请求过来之后,一个Filter的工作流程

  • 首先进入filter,执行相关业务逻辑
  • 若判定通行,则进入Servlet逻辑,Servlet执行完毕之后,又返回Filter,最后在返回给请求方
  • 判定失败,直接返回,不需要将请求发给Servlet

过滤器里面的三个方法 init : filter对象只会创建一次,init方法也只会执行一次。 doFilter : 主要的业务代码编写方法,可以多次重复调用 destroy : 在销毁Filter时自动调用(程序关闭或者主动销毁Filter)。

其它模块按照上面方法配置

首先是商品模块

删除依赖配置注解后,由于每个模块是独立服务的,所以需要Filter,将用户模块的Filter直接复制过来即可生效。。。

其次是订单模块

继续检查:

同理还有product的pojo类。。。

疑惑? 为了解决filter的耦合问题,把其它两个模块依赖删了,从而导入了其它模块的类,我寻思这不是因小失大,耦合更加严重了?

这里保留意见,而且这里filter每个模块都一样,都是过滤出用户信息,为何不直接放在公共模块,是不是这里只是比较特殊,每个模块其实有些项目不止用到用户信息过滤,还有其它信息,所以每个模块需要一个自己的过滤。。。

测试

启动Gateway网关,不启动zuul…

作业,实现获取JWT

参考: 项目实战生成JWT


其它测试就不放上来了,如果403就需要添加jwt_token。。。