赞
踩
我们的微服务网关其中一个作用便是统一入口,根据请求进行路由转发,将我们的请求解析到最终的微服务之中去
gateway网关为我们提供了丰富的路由谓词操作,根们我们的路由谓词进行请求转发
详情请查看>>>>>>>>spring-cloud(十一)GateWay强大的路由谓词(断言)功能
路由谓词转发示例:
spring:
cloud:
gateway:
routes:
# 路由ID
- id: location
order: -1
uri: lb://app-location-center
predicates:
# 当请求以/location/** 开头时,最终将被转发到上方url中
- Path=/location/**
filters:
# 截断一位url请求前缀
- StripPrefix=1
当然我们也可以不定义路由谓词进行请求转发,使用默认的微服务名进行转发(此种操作需要网关注册到注册中心)
根据微服务名-服务发现进行转发配置
spring:
cloud:
gateway:
discovery:
locator:
# 开启服务发现路由转发,会根据请求url中的服务名,将请求转发到对应的服务
enabled: true
gateway 依赖中默认包含了路由端点操作,我们只需要引入依赖即可查看
implementation 'org.springframework.boot:spring-boot-starter-actuator'
服务Yml配置
management:
endpoint:
gateway:
# gateway 端点开启
enabled: true
endpoints:
web:
exposure:
include: "gateway"
我们可以在yml中按住ctrl 点后点击enable查看gateway端点底层代码
端点源代码
我们发现,gateway端点中为我们提供了路由信息的CRUD操作
url如下:
# 获取网关已加载所有路由信息
GET http://localhost:8901/actuator/gateway/routes
# 根据路由ID获取路由信息
GET http://localhost:8901/actuator/gateway/routes/{id}
# 刷新路由信息
POST http://localhost:8901/actuator/gateway/refresh
# 新增路由信息
POST http://localhost:8901/actuator/gateway/routes/{id} 与路由json体
.....更详细的请自己查看源码
上方已经展示了路由端点API,接下来,咱们要实现路由持久化可以先看下原本默认API代码,熟悉其中的原理,然后进行改写
我们还是先来到AbstractGatewayControllerEndpoint
这个类中,查看路由新增做了什么事情
我们接下来看,进行理由保存的routeDefinitionWriter
从源码上看,其是注入的RouteDefinitionWriter
这个接口
protected RouteDefinitionWriter routeDefinitionWriter;
RouteDefinitionWriter
接口中提供了路由的新增与删除抽象方法
然后查看其实现类或者子接口
RouteDefinitionRepository
接口中又做了什么事情呢?查看源码,其无任何自身的抽象接口,仅仅继承了路由加载器接口RouteDefinitionLocator
(只有路由查询) 以及路由操作写入接口RouteDefinitionWriter
(只有路由新增与删除)
哦,懂了,其实RouteDefinitionRepository
就是将两个接口的抽象方法聚在了一起罢了
public interface RouteDefinitionRepository
extends RouteDefinitionLocator, RouteDefinitionWriter {
}
然后跟踪到最底层的实现类InMemoryRouteDefinitionRepository
,这就是路由操作的最终实现
我们发现,其对路由的增删查操作均是基于jvm内存操作的,并无任何持久化操作,这也是为何使用端点操作路由服务重启后,功能失效的原因
使用ApplicationEventPublisher
事件发布后,最终会被路由监听到,然后从路由加载器拉取最新路由信息
会从接口RouteDefinitionLocator
其每个子类实现的getRouteDefinitions
方法获取路由信息,然后加载到内存之中
比如我们的服务发现路由加载器
配置文件路由加载器
然后后续拉取则从内存缓存中拉取路由信息
持久化核心逻辑
写入持久层,刷新内存缓存,进行事件发布
package com.leilei.gateway.service; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.leilei.gateway.entity.Result; import com.leilei.gateway.entity.form.RouteForm; import com.leilei.gateway.entity.po.RouteInfo; import lombok.extern.log4j.Log4j2; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.cloud.gateway.event.RefreshRoutesEvent; import org.springframework.cloud.gateway.filter.FilterDefinition; import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.cloud.gateway.route.RouteDefinitionWriter; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.stereotype.Service; import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Mono; import java.net.URI; import java.util.List; import java.util.stream.Collectors; /** * @author lei * @create 2022-04-11 15:35 * @desc **/ @Service @Log4j2 public class DynamicRouteService implements ApplicationEventPublisherAware { private final RouteDefinitionWriter routeDefinitionWriter; private final RouteService routeService; private ApplicationEventPublisher publisher; public DynamicRouteService(RouteDefinitionWriter routeDefinitionWriter, RouteService routeService) { this.routeDefinitionWriter = routeDefinitionWriter; this.routeService = routeService; } /** * 增加路由 * * @param routeForm * @return */ public Result<Boolean> add(RouteForm routeForm) { RouteDefinition definition = convert(routeForm); routeDefinitionWriter.save(Mono.just(definition)).subscribe(); enduranceRule(routeForm.getName(), routeForm.getDescription(), definition); publishRouteEvent(); System.out.println(JSON.toJSONString(definition)); return Result.success(true); } /** * 更新路由 * * @param routeForm * @return */ public Result<Boolean> update(RouteForm routeForm) { RouteDefinition definition = convert(routeForm); try { this.routeDefinitionWriter.delete(Mono.just(definition.getId())).subscribe(); } catch (Exception e) { return Result.fail("未知路由信息"); } try { routeDefinitionWriter.save(Mono.just(definition)).subscribe(); enduranceRule(routeForm.getName(), routeForm.getDescription(), definition); publishRouteEvent(); return Result.success(true); } catch (Exception e) { return Result.fail("路由信息修改失败!"); } } /** * 删除路由 * * @param id * @return */ public Result<Boolean> delete(String id) { this.routeDefinitionWriter.delete(Mono.just(id)).subscribe(); routeService.remove(buildWrapper(id)); publishRouteEvent(); return Result.success(); } private void publishRouteEvent() { this.publisher.publishEvent(new RefreshRoutesEvent(this)); } public Result<Boolean> flushRoute() { publishRouteEvent(); return Result.success(true); } public LambdaQueryWrapper<RouteInfo> buildWrapper(String routeId) { return new QueryWrapper<RouteInfo>().lambda() .eq(RouteInfo::getRouteId, routeId) .last("limit 1"); } @Override public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } /** * 把自定义请求模型转换为 * * @param form * @return */ private RouteDefinition convert(RouteForm form) { RouteDefinition definition = new RouteDefinition(); definition.setId(form.getId()); definition.setOrder(form.getOrder()); //设置断言 List<PredicateDefinition> predicateDefinitions = form.getPredicates().stream() .distinct().map(predicateInfo -> { PredicateDefinition predicate = new PredicateDefinition(); predicate.setArgs(predicateInfo.getArgs()); predicate.setName(predicateInfo.getName()); return predicate; }).collect(Collectors.toList()); definition.setPredicates(predicateDefinitions); // 设置过滤 List<FilterDefinition> filterList = form.getFilters().stream().distinct().map(x -> { FilterDefinition filter = new FilterDefinition(); filter.setName(x.getName()); filter.setArgs(x.getArgs()); return filter; }).collect(Collectors.toList()); definition.setFilters(filterList); // 设置URI,判断是否进行负载均衡 URI uri; if (form.getUri().startsWith("http")) { uri = UriComponentsBuilder.fromHttpUrl(form.getUri()).build().toUri(); } else { uri = URI.create(form.getUri()); } definition.setUri(uri); return definition; } /** * 数据落库 */ public void enduranceRule(String name, String description, RouteDefinition definition) { String id = definition.getId(); List<PredicateDefinition> predicates = definition.getPredicates(); List<FilterDefinition> filters = definition.getFilters(); int order = definition.getOrder(); URI uri = definition.getUri(); RouteInfo routeInfo = new RouteInfo(); routeInfo.setName(name); routeInfo.setRouteId(id); routeInfo.setUri(uri.toString()); routeInfo.setPredicates(JSON.toJSONString(predicates)); routeInfo.setFilters(JSON.toJSONString(filters)); routeInfo.setEnabled(true); routeInfo.setDescription(description); routeInfo.setOrderNum(order); routeInfo.setDeleteFlag(false); RouteInfo one = routeService.getOne(buildWrapper(id)); if (one == null) { routeService.save(routeInfo); } else { routeInfo.setId(one.getId()); routeService.updateById(routeInfo); } } }
自定义路由操作(可以在此类进行持久化处理也可以像我上方一样,持久层交由其他服务类去做)
package com.leilei.gateway.service; import javassist.NotFoundException; import lombok.extern.log4j.Log4j2; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.cloud.gateway.route.RouteDefinitionRepository; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; /** * @author lei * @create 2022-04-11 16:20 * @desc 自定义内存路由管理仓,开启日志打印 **/ @Service @Log4j2 public class MyRouteDefinitionRepository implements RouteDefinitionRepository { public final Map<String, RouteDefinition> routes = Collections.synchronizedMap(new LinkedHashMap<>()); @Override public Flux<RouteDefinition> getRouteDefinitions() { Collection<RouteDefinition> values = routes.values(); return Flux.fromIterable(values); } @Override public Mono<Void> save(Mono<RouteDefinition> route) { return route.flatMap( r -> { log.info("新增路由信息:{}",r); routes.put(r.getId(), r); return Mono.empty(); }); } @Override public Mono<Void> delete(Mono<String> routeId) { return routeId.flatMap(id -> { log.info("删除路由信息,路由ID为:{}",id); if (routes.containsKey(id)) { routes.remove(id); return Mono.empty(); } return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: "+routeId))); }); } }
核心原理
启动项目,在从路由加载器RouteDefinitionLocator
各个子类拉取路由外,额外进入我们的路由持久层加载路由
改写上方的动态路由服务类,进行项目启动加载
package com.leilei.gateway.service; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.leilei.gateway.entity.Result; import com.leilei.gateway.entity.form.RouteForm; import com.leilei.gateway.entity.po.RouteInfo; import lombok.extern.log4j.Log4j2; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.cloud.gateway.event.RefreshRoutesEvent; import org.springframework.cloud.gateway.filter.FilterDefinition; import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.cloud.gateway.route.RouteDefinitionWriter; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.stereotype.Service; import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Mono; import java.net.URI; import java.util.List; import java.util.stream.Collectors; /** * @author lei * @create 2022-04-11 15:35 * @desc **/ @Service @Log4j2 public class DynamicRouteService implements ApplicationEventPublisherAware, ApplicationRunner { private final RouteDefinitionWriter routeDefinitionWriter; private final RouteService routeService; private ApplicationEventPublisher publisher; public DynamicRouteService(RouteDefinitionWriter routeDefinitionWriter, RouteService routeService) { this.routeDefinitionWriter = routeDefinitionWriter; this.routeService = routeService; } /** * 增加路由 * * @param routeForm * @return */ public Result<Boolean> add(RouteForm routeForm) { RouteDefinition definition = convert(routeForm); routeDefinitionWriter.save(Mono.just(definition)).subscribe(); enduranceRule(routeForm.getName(), routeForm.getDescription(), definition); publishRouteEvent(); System.out.println(JSON.toJSONString(definition)); return Result.success(true); } /** * 更新路由 * * @param routeForm * @return */ public Result<Boolean> update(RouteForm routeForm) { RouteDefinition definition = convert(routeForm); try { this.routeDefinitionWriter.delete(Mono.just(definition.getId())).subscribe(); } catch (Exception e) { return Result.fail("未知路由信息"); } try { routeDefinitionWriter.save(Mono.just(definition)).subscribe(); enduranceRule(routeForm.getName(), routeForm.getDescription(), definition); publishRouteEvent(); return Result.success(true); } catch (Exception e) { return Result.fail("路由信息修改失败!"); } } /** * 删除路由 * * @param id * @return */ public Result<Boolean> delete(String id) { this.routeDefinitionWriter.delete(Mono.just(id)).subscribe(); routeService.remove(buildWrapper(id)); publishRouteEvent(); return Result.success(); } private void publishRouteEvent() { this.publisher.publishEvent(new RefreshRoutesEvent(this)); } public Result<Boolean> flushRoute() { publishRouteEvent(); return Result.success(true); } public LambdaQueryWrapper<RouteInfo> buildWrapper(String routeId) { return new QueryWrapper<RouteInfo>().lambda() .eq(RouteInfo::getRouteId, routeId) .last("limit 1"); } @Override public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } @Override public void run(ApplicationArguments args) { log.info("----------从持久层加载额外路由信息---------"); this.loadRouteConfig(); } private void loadRouteConfig() { List<RouteDefinition> routeList = routeService.getRouteList(); log.info("----------持久层额外路由数量:{}---------",routeList.size()); routeList.forEach(x -> routeDefinitionWriter.save(Mono.just(x)).subscribe()); publishRouteEvent(); } /** * 把自定义请求模型转换为 * * @param form * @return */ private RouteDefinition convert(RouteForm form) { RouteDefinition definition = new RouteDefinition(); definition.setId(form.getId()); definition.setOrder(form.getOrder()); //设置断言 List<PredicateDefinition> predicateDefinitions = form.getPredicates().stream() .distinct().map(predicateInfo -> { PredicateDefinition predicate = new PredicateDefinition(); predicate.setArgs(predicateInfo.getArgs()); predicate.setName(predicateInfo.getName()); return predicate; }).collect(Collectors.toList()); definition.setPredicates(predicateDefinitions); // 设置过滤 List<FilterDefinition> filterList = form.getFilters().stream().distinct().map(x -> { FilterDefinition filter = new FilterDefinition(); filter.setName(x.getName()); filter.setArgs(x.getArgs()); return filter; }).collect(Collectors.toList()); definition.setFilters(filterList); // 设置URI,判断是否进行负载均衡 URI uri; if (form.getUri().startsWith("http")) { uri = UriComponentsBuilder.fromHttpUrl(form.getUri()).build().toUri(); } else { uri = URI.create(form.getUri()); } definition.setUri(uri); return definition; } /** * 数据落库 */ public void enduranceRule(String name, String description, RouteDefinition definition) { String id = definition.getId(); List<PredicateDefinition> predicates = definition.getPredicates(); List<FilterDefinition> filters = definition.getFilters(); int order = definition.getOrder(); URI uri = definition.getUri(); RouteInfo routeInfo = new RouteInfo(); routeInfo.setName(name); routeInfo.setRouteId(id); routeInfo.setUri(uri.toString()); routeInfo.setPredicates(JSON.toJSONString(predicates)); routeInfo.setFilters(JSON.toJSONString(filters)); routeInfo.setEnabled(true); routeInfo.setDescription(description); routeInfo.setOrderNum(order); routeInfo.setDeleteFlag(false); RouteInfo one = routeService.getOne(buildWrapper(id)); if (one == null) { routeService.save(routeInfo); } else { routeInfo.setId(one.getId()); routeService.updateById(routeInfo); } } }
网关启动,一开始并无额外动态路由信息
查看所有网关加载路由
http://localhost:8901/actuator/gateway/routes
[ { "predicate": "Paths: [/location/**], match trailing slash: true", "route_id": "location", "filters": [ "[[StripPrefix parts = 1], order = 1]" ], "uri": "lb://app-location-center", "order": -1 }, { "predicate": "Paths: [/app-location-center/**], match trailing slash: true", "metadata": { "nacos.instanceId": null, "nacos.weight": "1.0", "nacos.cluster": "DEFAULT", "nacos.ephemeral": "true", "nacos.healthy": "true", "preserved.register.source": "SPRING_CLOUD" }, "route_id": "ReactiveCompositeDiscoveryClient_app-location-center", "filters": [ "[[RewritePath /app-location-center/(?<remaining>.*) = '/${remaining}'], order = 1]" ], "uri": "lb://app-location-center", "order": 0 }, { "predicate": "Paths: [/dodo-server-gateway/**], match trailing slash: true", "metadata": { "nacos.instanceId": null, "nacos.weight": "1.0", "nacos.cluster": "DEFAULT", "nacos.ephemeral": "true", "nacos.healthy": "true", "preserved.register.source": "SPRING_CLOUD" }, "route_id": "ReactiveCompositeDiscoveryClient_dodo-server-gateway", "filters": [ "[[RewritePath /dodo-server-gateway/(?<remaining>.*) = '/${remaining}'], order = 1]" ], "uri": "lb://dodo-server-gateway", "order": 0 }, { "predicate": "Paths: [/app-base-center/**], match trailing slash: true", "metadata": { "preserved.heart.beat.timeout": "10000", "nacos.instanceId": null, "nacos.weight": "1.0", "nacos.cluster": "DEFAULT", "nacos.ephemeral": "true", "nacos.healthy": "true", "preserved.ip.delete.timeout": "15000", "preserved.register.source": "SPRING_CLOUD", "preserved.heart.beat.interval": "5000" }, "route_id": "ReactiveCompositeDiscoveryClient_app-base-center", "filters": [ "[[RewritePath /app-base-center/(?<remaining>.*) = '/${remaining}'], order = 1]" ], "uri": "lb://app-base-center", "order": 0 } ]
访问预添加路由信息
添加路由并持久化
{ "name":"bilibili路由", "description":"测试自定义路由信息", "filters": [{ "name": "StripPrefix", "args": { "_genkey_0": "1" } }], "id": "test-route", "uri": "https://www.bilibili.com/anime/?spm_id_from=333.1007.0.0", "order": 2, "predicates": [{ "name": "Path", "args": { "_genkey_0": "/b3" } }] }
数据也已经落到了DB
打印了路由新增信息(加入到内存)
查看网关已加载路由信息
…
再次访问
我们再次访问http://localhost:8901/b3
网关已经帮我们路由到了我们定义的url了(定义的bilibili)
修改路由后再次访问
发现网关路由信息成功修改,为我们转发到了csdn(虽然css样式丢失了)
项目重启,测试路由从持久层加载
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。