SpringCloud之Gateway
1. 什么是Gateway
Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代 Netflflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流。
优点:
l 性能强劲:是第一代网关Zuul的1.6倍
l 功能强大:内置了很多实用的功能,例如转发、监控、限流等
l 设计优雅,容易扩展
缺点:
l 其实现依赖Netty与WebFlux,不是传统的Servlet编程模型,学习成本高
l 不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行
l 需要Spring Boot 2.0及以上的版本,才支持
2. Gateway--服务网关
大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。
这样的架构,会存在着诸多的问题:
l 客户端多次请求不同的微服务,增加客户端代码或配置编写的复杂性
l 认证复杂,每个服务都需要独立认证。
l 存在跨域请求,在一定场景下处理相对复杂。
上面的这些问题可以借助API网关来解决。
所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服 务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。 添加上API网关之后,系统的架构图变成了如下所示:
3. 使用Gateway
单独创建GatewayMaven工程
【注:Gateway本质来说也是一个子服务模块】
【注:Gateway是和xw-serve、xw-comm服务模块同级】
在其pom文件加入依赖:
<!--加入gateway的依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
【注:还有在工程中创建启动类】
3.1 进行Gateway配置
创建application.yml配置文件在其中配置子服务模块信息:
【注:可以是yml后缀的配置文件,因为用properties后缀的配置文件比较麻烦】
3.1.1 复杂配置设置:
server: port: 8851 spring: application: name: gateway #服务名称 # 配置子服务模块api #【注:可以配置多个子服务模块】 cloud: gateway: routes: - id: order # 路由的唯一标识,只要不重复都可以,如果不写默认会通过UUID产生,一般写成被路由的服务名称 uri: http://localhost:8082/ # 被路由的地址(去向子服务模块的地址+端口) order: 1 #表示优先级 数字越小优先级越高 predicates: #断言: 执行路由的判断条件 - Path=/product_serv/** filters: # 过滤器: 可以在请求前或请求后作一些手脚 - StripPrefix=1
3.1.2 简单配置设置及Gateway前后端跨域:
server: port: 8851 spring: application: name: gateway # 配置api cloud: gateway: globalcors: cors-configurations: '[/**]': allowedOriginPatterns: "*" allowedHeaders: "*" allowedMethods: "*" allowCredentials: true # default-filters: # - DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST discovery: locator: enabled: true
【注:使用Gateway本身不能跟web依赖共存】
【注:使用Gateway只需要其本身配置跨域信息即可,子服务模块不需要配置跨域信息】
【注:一切都以通过Gateway去访问子服务模块】
访问路径:ip地址:Gateway端口号/子服务模块微服务微服务名称/要访问的子服务路径
3.2 自定义Gateway全局拦截器
创建com.aaa包下config包下
AuthGlobalFilter
配置类:com -> aaa -> congif ->
AuthGlobalFilter
@Component public class AuthGlobalFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //获取请求头中的token值 ServerHttpRequest request = exchange.getRequest(); String token = request.getHeaders().getFirst("token"); //判断token是否为空 if (StringUtils.isEmpty(token)){ exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); }else { //不为空则校验token,校验通过将token存入Redis中 try { Claims claims = JwtUtils.parseJWT(token); return chain.filter(exchange); }catch (Exception e){ e.printStackTrace(); throw new RuntimeException("token非法"); } } } @Override public int getOrder() { return 0; } }
【注:此配置文件作为Gateway检测前端每次发起请求是否携带token】
【注:此配置文件后期可根据业务需求进行更改拦截条件】
4. 将heads注入容器获取token
注:【假如有需求需要使用到用户id或用户信息从token中解析取值】
该如何?
通过注入HTTPServletRequest 来获取request 对象:
@Resource private HttpServletRequest request;
String token = request.getHeader("token");//从请求头中获取token Claims claims = JwtUtils.parseJWT(token);//解析token // 将这个map转化为对象 List token1 = (List) claims.get("token"); Map o = (Map) token1.get(0); user user = BeanUtil.fillBeanWithMap(o, new user(), false);//将集合转换对象 int id = user.getId();//获取到了用户id
5. Gateway白名单
通过在application.yml配置文件中定义白名单:
#配置Gateway白名单 whitename: /log/login #要加入白名单的路径 #【注:因为是通过Gateway访问,所以路径格式应是:/子服务模块的服务名称/要访问的路径】
在Gateway配置类中进行获取白名单的值:
AuthGlobalFilter
@Component public class AuthGlobalFilter implements GlobalFilter, Ordered { //获取application.yml中定义的白名单且将其注入到whitename @Value("${whitename}") private String whitename; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //设置白名单 String[] whitenames = whitename.split(","); // 判断请求的路径 // header // 获取请求头中的请求路径 ServerHttpRequest requestss = exchange.getRequest(); String path = requestss.getURI().getPath(); System.out.println("请求的路径:" + path); if (ArrayUtil.contains(whitenames, path)) { // 放行:如果请求头中的请求路径在白名单中则直接放行 //否则执行else逻辑 return chain.filter(exchange); } else{ //获取请求头中的token值 ServerHttpRequest request = exchange.getRequest(); String token = request.getHeaders().getFirst("token"); //判断token是否为空 if (StringUtils.isEmpty(token)){ exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); }else { //不为空则校验token,校验通过将token存入Redis中 try { Claims claims = JwtUtils.parseJWT(token); return chain.filter(exchange); }catch (Exception e){ e.printStackTrace(); throw new RuntimeException("token非法"); } } } } @Override public int getOrder() { return 0; } }
6. OpenFeign组件之间调度优化
在SpringCloud中,组件与组件之间通过OpenFeign调度实现
那么在我们固有的返回格式中
那么调用者从被调者组件获取数据时返回的是一个固有的result对象
那么我们还需要从result对象中提取数据还要进行对象转换才能获取到我们想要的值:
如下:
OpenFeign层:
@FeignClient(value = "prodect",fallback = ProdectService.class) public interface OrderService { @RequestMapping("/sep/{sid}") result GetProdectMsg(@PathVariable Integer sid); }
controller层:
//查询商品 @RequestMapping("/sep/{sid}") public result GetProdectMsg(@PathVariable Integer sid){ //将sid通过OpenFeign来传入被调者组件中,且将结果以result对象形式返回 result forObject = orderService.GetProdectMsg(sid); Object data = forObject.getData();//获取result中的data数据 ObjectMapper objectMapper = new ObjectMapper(); Prodect prodect = objectMapper.convertValue(data, Prodect.class);//进行数据类别转换得到我们需要的值 return new result(200,"商品查询成功!!",prodect); }
如此繁琐的操作让我们不得不思考是否更可以进一步对OpenFeign优化:
其实,我们在组件与组件之间的调用时,通常会在被调用者中创建一个api层,专门用来用于组件之间的相互调用:
调用者:
OpenFeign层:
@FeignClient(value = "prodect",fallback = ProdectService.class) public interface OrderService { @RequestMapping("/mypro/{sid}") Prodect GetProdectMsg(@PathVariable Integer sid); }
controller层:
//查询商品 @RequestMapping("/sep/{sid}") public result GetProdectMsg(@PathVariable Integer sid){ //将sid通过OpenFeign来传入被调者组件中,且将结果直接以prodect对象形式返回 Prodect prodect = orderService.GetProdectMsg(sid); return new result(200,"商品查询成功!!",prodect); }
被调用者:
以上便是SpringCloud之Gateway中的内容,如有漏缺请在下方留言告知,我会及时补充