tips:本文完全来源于卢泽龙!!!
一、Gateway概述
1.1设计目标
1.2gateway基本功能
中文文档参考:https://cloud.tencent.com/developer/article/1403887?from=15425
三大核心:
二、引入依赖和yaml配置
1.1maven依赖(大坑:spring-web和actuaor一定不要引入!)
<!--gateway-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
1.2BootStrap.yaml配置
spring:application:name: rical-gatewaycloud:nacos:discovery:server-addr: xx.xxx.xx.xx:8848config:server-addr: xx.xxx.xx.xx:8848 #Nacos作为配置中心地址file-extension: yaml #指定yaml格式的配置
1.3动态路由配置rical-gateway-dev.yaml(nacos配置)
spring:cloud:gateway:routes:- id: search-movieInfouri: https://www.maoyan.compredicates:- Path=/films/**- id: self-checkuri: http://localhost:8080predicates:- Path=/demo/selfCheck.jsonrical:gateway:request:qpslimit: 1timeout: 0
三、自定义Filter
3.1、gateWay网关扩展点介绍
使用到gateway过滤器:
3.2、全局限流器
通过在nacos平台的rical-gateway-dev.yaml配置qpslimit和timeout这两个参数,实现限制qps和限流后的等待时间。遭到限制时,直接给客户端返回error响应!
@Component
@Slf4j
public class LimitFilter implements GlobalFilter, Ordered
{@Value("${rical.gateway.request.qpslimit}")private Double qpslimit;@Value("${rical.gateway.request.timeout}")private Integer timeout;//创建一个限流器,参数代表每秒生成的令牌数(用户限流频率设置 每秒中限制qpslimit个请求)RateLimiter rateLimiter;/*** 为什么要在bean的初始化方法赋值?* 因为@value注解是在spring的 populateBean 方法中 通过 AutowiredAnnotationBeanPostProcessor后置处理器中赋值* * 而如果直接在上面赋值他的执行时机是jvm启动时赋值,该步骤会在spring之前就会产生npe异常*/@PostConstructpublic void init(){rateLimiter = RateLimiter.create(qpslimit);}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpResponse response = exchange.getResponse();ServerHttpRequest request = exchange.getRequest();HttpHeaders header = response.getHeaders();header.add("Content-Type", "application/json; charset=UTF-8");RequestPath path = request.getPath();//设置等待超时时间的方式获取令牌,如果超timeout为0,则代表非阻塞,获取不到立即返回boolean tryAcquire = rateLimiter.tryAcquire(timeout, TimeUnit.SECONDS);log.info("com.wtrue.rical.gateway.filter.LimitFilter.filter , tryAcquire = {}",tryAcquire);if (!tryAcquire) {JSONObject jsonObject = setResultErrorMsg("当前访问用户过多,请稍后重试");DataBuffer buffer = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes());return response.writeWith(Mono.just(buffer));}// 放行return chain.filter(exchange);}private JSONObject setResultErrorMsg(String msg) {JSONObject jsonObject = new JSONObject();jsonObject.put("code", "406");jsonObject.put("message", msg);return jsonObject;}@Overridepublic int getOrder(){return 0;}
}
3.3、全局日志打印器
注意:该日志打印器只打印配置好路由且没有被上面限流器限流的http请求(比如用户随便乱输入个路径进行请求是不会打印日志的)
@Component
@Slf4j
public class LoggerFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1.获取请求信息ServerHttpRequest request = exchange.getRequest();InetSocketAddress address = request.getRemoteAddress();String method = request.getMethodValue();URI uri = request.getURI();HttpHeaders requestHeaders = request.getHeaders();// 获取请求queryMultiValueMap<String, String> map = request.getQueryParams();log.info("LoggerFilter.filter , request come in, please look down: \n{\n\trequest = {} \n\taddress = {} \n\tmethod = {} \n\turi = {} \n\trequestHeaders = {} \n\tmap = {}\n}",request,address,method,uri,requestHeaders,map);// 2.获取响应信息ServerHttpResponse response = exchange.getResponse();HttpStatus statusCode = response.getStatusCode();MultiValueMap<String, ResponseCookie> cookies = response.getCookies();HttpHeaders responseHeaders = response.getHeaders();log.info("LoggerFilter.filter , response is : \n{\n\tstatusCode = {} \n\tcookies = {} \n\tresponseHeaders = {}\n}",statusCode,cookies,responseHeaders);return chain.filter(exchange);}@Overridepublic int getOrder() {return 1;}
}
四、服务测试(类似美团OCTO功能)
1、大体流程图
2、具体实现方法
2.1gateway的export包提供一个所有业务方都能使用的controller,并通过springBoot自动装配的办法让业务方一引入依赖就能使用
@RestController
@Slf4j
public class SelfCheckController implements BeanFactoryAware {/*** 业务方引入该export包,就会把业务方自己的appname放进去*/@Value("${spring.application.name}")private String appName;private BeanFactory beanFactory;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}@PostMapping("selfCheck.json")public String testInvoke(@RequestBody SelfCheckRequest request) {try {//1.安全检测invokeSafeCheck(request);//2.获取目标beanClass<?> beanClazz = Class.forName(request.getBeanClazzName());Object tartgetBean = getTartgetBean(beanClazz);//3.转化参数类型列表Class[] parameterTyps = transformParameterTypes(request.getParameterTypes());//4.执行Method method = beanClazz.getMethod(request.getMethodName(), parameterTyps);Object returnValue = method.invoke(tartgetBean, request.getParameters());return JSON.toJSONString(returnValue);}catch (Exception e){log.error("errMsg : {}",e.getMessage(),e);return "自检异常:" + e.getMessage();}}/*** 转化参数类型列表* @param parameterTypes* @return*/@SneakyThrowsprivate Class[] transformParameterTypes(String[] parameterTypes) {if (ArrayUtils.isEmpty(parameterTypes)) {return new Class[0];}Class[] classes = new Class[parameterTypes.length];int index = 0;for (String parameterType : parameterTypes) {Class<?> paramClzz = Class.forName(parameterType);classes[index++] = paramClzz;}return classes;}/*** 获取目标bean* @param beanClazz* @return*/private Object getTartgetBean(Class<?> beanClazz) {Object bean = beanFactory.getBean(beanClazz);if (bean == null){throw new RuntimeException("本服务没有自检所需要的bean");}return bean;}/*** 执行前的安全检测* @param request*/private void invokeSafeCheck(SelfCheckRequest request) {String targetAppName = request.getAppName();if (!targetAppName.equals(appName)) {throw new RuntimeException("自检错误!本服务不是目标服务,请检查appName是否传递错误~");}}}
2.2 业务方一定要配置这两个参数,后面有大用
server.servlet.context-path=/demo
spring.application.name=demo
2.3 nacos配置路由规则
- id: self-checkuri: http://localhost:8080predicates:- Path=/demo/selfCheck.json
经过这样配置后,用户就能通过网关访问目标服务器的bean方法了,请求案例如下
POST localhost:10001/demo/selfCheck.json
{"appName": "demo","beanClazzName": "com.example.demo.service.ITestService","methodName": "getTestMap","parameterTypes": ["java.lang.String","java.lang.Integer"],"parameters": ["luzelong",999]
}
目标方法的代码如下:
public Map getTestMap(String key, Integer value) {HashMap<String, Integer> map = new HashMap<>();map.put(key,value);log.info("demo!!!!!!");return map;
}