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);
}