springcloud alibab-网关
1.微服务容易出现的问题
(1)客户端要维护服务端的各个地址,代码困难
(2)认证,鉴权复杂
(3)跨域问题
2.为了解决上述问题,所以引入api网关,它的作用就是提供系统的统一入口,封装程序的内部结构,为客户端提供统一服务,一些与业务本身的功能的公共逻辑就可以在这里实现,例如:认证,鉴权,监控和路由转发
3.流程
(1)新建项目api-gateway
(2)导入jar包
<!--此模块不能引入web包--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId> </dependency>
记住,千万不要导入web包,会冲突
(3)在启动类上加入@EnableDiscoveryClient
(4)修改配置文件
spring: application: name: api-gateway cloud: nacos: discovery: server-addr: localhost:8848 gateway: discovery: locator: enabled: true #可以让gateway发现nacos中的微服务 routes: #路由数组,就是指当前请求满足什么条件时转到哪个微服务 - id: product_route #当前路由的标识,唯一 #uri: http://localhost:8081 #请求要转发的地址 uri: lb://service-product #lb:负载均衡 后面跟着的是微服务的具体标识 order: 1 #路由的优先级,数字越小级别越高 predicates: #断言,就是路由转发要满足的条件 - Path=/product-serv/** #当请求路径满足Path指定的规则时,才能进行路由转发 filters: #过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改 - StripPrefix=1 #转发之前去掉一层路径
4.断言
就是说在什么条件下才能执行真正的路由
内置断言工厂
(1)基于DateTime类型的断言工厂
(2)基于远程地址的断言工厂
(3)基于cookie的断言工厂
(4)基于head的断言工厂
(5)基于host的断言工厂
(6)基于Methon请求方法的断言工厂
(7)基于path请求路径的断言工厂
(8)基于jquery请求参数的断言工厂
(9)基于路由权重的断言工厂
自定义断言工厂
(1)在配置文件中添加断言配置 - Age=18,60
(2)自定义断言工厂,实现断言方法
import org.apache.commons.lang.StringUtils; import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; /** * 泛型,用于接收一个配置类,配置类用于接收配置文件中的配置 */ @Component public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> { public AgeRoutePredicateFactory() { super(AgeRoutePredicateFactory.Config.class); } /** * 用于配置文件中获取参数值赋值到配置类中到属性上 * @return */ @Override public List<String> shortcutFieldOrder() { //这里的配置要求和配置文件中的顺序一致 return Arrays.asList("minAge", "maxAge"); } @Override public Predicate<ServerWebExchange> apply(AgeRoutePredicateFactory.Config config) { return new Predicate<ServerWebExchange>() { //从serverWebExchange获取传入的参数 @Override public boolean test(ServerWebExchange serverWebExchange) { String ageStr = serverWebExchange.getRequest().getQueryParams().getFirst("age"); if (StringUtils.isNotEmpty(ageStr)) { int age = Integer.parseInt(ageStr); return age > config.getMinAge() && age < config.getMaxAge(); } return true; } }; } @Data
public static class Config {
private Integer minAge;
private Integer maxAge;
}
}
5.过滤器
作用:在请求的传递过程中,对请求和响应做一些手脚
生命周期:Pre(身份认证,记录访问调试信息)和Post(添加请求头,收集统计信息和指标)
分类:局部过滤器(作用在某一路由上)(GatewayFilter),全局过滤器(作用在全部路由上)(GlabolFilter)
内置局部过滤器
AddRequestHeader 为原始请求添加Header
AddRequestParamter 为原始请求添加请求参数
AddResponseHeader 为原始请求添加Header
SetStatus 为原始请求添加状态码
。。。
自定义局部过滤器
(1)修改配置文件
- Log=true, false #控制日志是否开启
(2)自定义一个过滤工厂,实现方法
import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.Arrays; import java.util.List; /** * 自定义局部过滤器 */ @Component public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> { public LogGatewayFilterFactory() { super(LogGatewayFilterFactory.Config.class); } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("consoleLog", "cacheLog"); } @Override public GatewayFilter apply(LogGatewayFilterFactory.Config config) { return new GatewayFilter() { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { if(config.isCacheLog()) { System.out.println("cache日志已开启"); } if(config.isConsoleLog()) { System.out.println("console日志已开启"); } return chain.filter(exchange); } }; } /** * 自定义内部类,用来接收参数 */ @Data @NoArgsConstructor public class Config{ private boolean consoleLog; private boolean cacheLog; } }
全局过滤器
内置全局过滤器
无需配置
uri: lb://service-product #lb:负载均衡 后面跟着的是微服务的具体标识
这就是一个全局过滤器
自定义全局过滤器
鉴权
/** * 自定义全局过滤器(统一鉴权) */ @Slf4j @Component public class AuthGlobalFilter implements GlobalFilter, Ordered { /** * 用于编写过滤器的逻辑 * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getQueryParams().getFirst("token"); if (!StringUtils.equals("admin",token)){ //认证失败 log.info("认证失败了。。。"); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } /** * 用来标识当前过滤器的优先级 * @return */ @Override public int getOrder() { return 0; } }
6.网关限流
(1)基于路由维度的限流(在配置文件中配置的路由条目,资源名为对应的routeId)
导入依赖
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId> </dependency>
编写配置类
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import javax.annotation.PostConstruct; import java.util.*; @Configuration public class GatewayConfiguration { private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public GatewayConfiguration(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolvers; this.serverCodecConfigurer = serverCodecConfigurer; } /** * 初始化一个限流过滤器 * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } @PostConstruct public void initGatewayRules() { Set<GatewayFlowRule> rules = new HashSet<>(); rules.add(new GatewayFlowRule("product-rule") //资源名称,对应的路由id .setCount(1) //限流阈值 .setIntervalSec(1)); //统计时间窗口 GatewayRuleManager.loadRules(rules); } /** * 配置限流的异常处理器 * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } @PostConstruct public void initBlockHandlers() { BlockRequestHandler blockRequestHandler = new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) { Map map = new HashMap(); map.put("code", 0); map.put("message", "接口被限流了"); return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON_UTF8) .body(BodyInserters.fromObject(map)); } }; GatewayCallbackManager.setBlockHandler(blockRequestHandler); } }
(2)自定义api维度(用户可以用sentinel提供的api来自定义一些api分组)
@PostConstruct public void initGatewayRules() { Set<GatewayFlowRule> rules = new HashSet<>(); rules.add(new GatewayFlowRule("product_api1").setCount(1).setIntervalSec(1)); rules.add(new GatewayFlowRule("product_api2").setCount(1).setIntervalSec(1)); GatewayRuleManager.loadRules(rules); } /** * 自定义api分组 */ @PostConstruct private void initCustomizedApis() { Set<ApiDefinition> definitions = new HashSet<>(); ApiDefinition apiDefinition1 = new ApiDefinition("product_api") .setPredicateItems(new HashSet<ApiPredicateItem>(){{ //以/product-serv/product/api1开头的请求 add(new ApiPathPredicateItem().setPattern("/product-serv/product/api1/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); ApiDefinition apiDefinition2 = new ApiDefinition("product_api2") .setPredicateItems(new HashSet<ApiPredicateItem>(){{ //以/product-serv/product/api2/demo1 完整的url路径匹配 add(new ApiPathPredicateItem().setPattern("/product-serv/product/api2/demo1") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); definitions.add(apiDefinition1); definitions.add(apiDefinition2); GatewayApiDefinitionManager.loadApiDefinitions(definitions); }