入门学习
简介
为什么需要服务网关
API网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题:
-
客户端会多次请求不同的微服务,增加了客户端的复杂性。
-
存在跨域请求,在一定场景下处理相对复杂。
-
认证复杂,每个服务都需要独立认证。
-
难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施。
-
某些微服务可能使用了防火墙 / 浏览器不友好的协议,直接访问会有一定的困难。 以上这些问题可以借助 API网关解决。API网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 API网关这一层。
什么是 gateway
Spring cloud gateway 是 spring官方基于 Spring 5.0、Spring Boot2.0 和 Project Reactor 等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式,
Spring Cloud Gateway 作为 Spring Cloud生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且还基于 Filer链的方式提供了网关基本的功能,例如:安全、监控/埋点、限流等,
服务网关也是要注册到注册中心的。即外部调用者访问统一路径,然后 gateway 去找到 nacos 中的对应服务请求服务调用。
断言
设置断言规则,进行对应路由规则的匹配。
Query
就是请求路径里面有指定参数,或者有指定参数且值等于某个或某些值。
实例1
当请求路径中包含 url 这个参数,且参数值等于 baidu,那我就把 http://localhost:88/hello?url=baidu 转换成去请求 https://www.baidu.com/hello,就是说把前面的 http://localhost:88 这一块做替换。
server: port: 88 spring: cloud: gateway: routes: - id: test_route uri: https://www.baidu.com predicates: # 根据请求参数做断言 - Query=url,baidu # 高精确度匹配的要放在前面 - id: product_route uri: lb://smilemall-product predicates: - Path=/api/product/** filters: - RewritePath=/api/(?<segment>.*),/$\{segment} - id: renren_route # load balance 负载均衡 服务名称 uri: lb://renren-fast predicates: # 根据路径做断言 - Path=/api/** filters: # 路径重写 - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
跨域
使用网关的时候肯定会遇到跨域问题。
简单使用
实例1
创建 gateway模块
创建 service-gateway模块,该模块专用于服务网关。
引入依赖
在该 gateway模块中引入依赖。
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!-- 服务注册 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies>
配置文件
在该 gateway模块中配置配置文件。
server: # 服务端口 port: 80 spring: application: # 服务名 name: service-gateway cloud: nacos: discovery: # nacos服务地址 server-addr: localhost:8848 gateway: discovery: locator: #使用服务发现路由 enabled: true routes: #设置路由id - id: service-hospital #设置路由的uri,lb就是 LoadBalance的意思 uri: lb://service-hospital #设置路由断言,代理 servicerId为 auth-service的 /auth/路径 predicates: - Path=/*/hospital/** - id: service-cmn #设置路由的uri uri: lb://service-cmn #设置路由断言,代理 servicerId为 auth-service的 /auth/路径 predicates: - Path=/*/cmn/**
运行启动类
运行启动类即可启动网关,就可以代替 nginx 了。统一访问地址是 http://localhost:80,而因为是 80端口,所以 :80可以省略。
网关解决跨域
@Configuration public class CorsConfig { @Bean public CorsWebFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedMethod("*"); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser()); source.registerCorsConfiguration("/**", config); return new CorsWebFilter(source); } }
网关做登录校验
-
创建用户模块,注册到注册中心
-
在 gateway模块中配置 user模块的网关
-
网关模块中添加 filter,在 filter 中做设置。未登录的 code 是 208,
@Component public class AuthGlobalFilter implements GlobalFilter, Ordered { private AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); // 得到请求路径 String path = request.getURI().getPath(); System.out.println("===" + path); // 内部服务接口,不允许外部访问 if (antPathMatcher.match("/**/inner/**", path)) { ServerHttpResponse response = exchange.getResponse(); return out(response, ResultCodeEnum.PERMISSION); } Long userId = this.getUserId(request); // api接口,异步请求,校验用户必须登录 if (antPathMatcher.match("/api/**/auth/**", path)) { if (StringUtils.isEmpty(userId)) { ServerHttpResponse response = exchange.getResponse(); return out(response, ResultCodeEnum.LOGIN_AUTH); } } return chain.filter(exchange); } @Override public int getOrder() { return 0; } /** * api接口鉴权失败返回数据 * * @param response * @return */ private Mono<Void> out(ServerHttpResponse response, ResultCodeEnum resultCodeEnum) { Result result = Result.build(null, resultCodeEnum); byte[] bits = JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8); DataBuffer buffer = response.bufferFactory().wrap(bits); //指定编码,否则在浏览器中会中文乱码 response.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); return response.writeWith(Mono.just(buffer)); } /** * 获取当前登录用户id * * @param request * @return */ private Long getUserId(ServerHttpRequest request) { String token = ""; List<String> tokenList = request.getHeaders().get("token"); if (null != tokenList) { token = tokenList.get(0); } if (!StringUtils.isEmpty(token)) { return JwtHelper.getUserId(token); } return null; } }
-
修改前端的 utils/request.js
-
实例2