学习gateway网关时,是以产品应用为目的,打算做一个类似于SAAS平台,网关负责统一的鉴权,日志记录,对外屏蔽真实的访问地址。路由信息也不能是写死在配置文件的,必须是提供管理页面可维护的。所以就略过配置文件,直接开启动态路由的实现。
我的springboot及springCloud版本
org.springframework.boot spring-boot-dependencies2.6.11 pom import org.springframework.cloud spring-cloud-dependencies2021.0.1 pom import
依赖包
org.springframework.cloud spring-cloud-starter-gatewayorg.springframework.cloud spring-cloud-starter-loadbalancercom.alibaba.cloud spring-cloud-starter-alibaba-nacos-discoverycom.alibaba.cloud spring-cloud-starter-alibaba-nacos-config
开启服务与发现,路由的真实地址可设置为“lb:{应用名称}”格式的地址,可实现通过fegin进行负载均衡地址访问
spring: cloud: nacos: discovery: ##配置服务与发现 server-addr: 127.0.0.1:8848 namespace: d5afac56-78a0-48e5-ac76-c6e13c96f35f gateway: discovery: locator: ##指定是否启用服务发现定位器。当设置为true时,Gateway将通过服务发现来定位后端服务 enabled: true
路由信息的持久化此处不做解释,无非就是将json格式的路由数据保存到数据库,以下代码有将JSON格式的路由数据转换为路由对象的方法。以下代码为维护路由信息的业务层,包含路由信息的增、删、改、刷新等方法。
package com.zhangzz.gateway.route.service.impl; import com.alibaba.fastjson2.JSON; import com.zhangzz.gateway.domain.CustomPredicateDefinition; import com.zhangzz.gateway.domain.CustomRouteDefinition; import com.zhangzz.gateway.domain.RouteInfo; import com.zhangzz.core.entity.AjaxResult; import com.zhangzz.gateway.route.service.IRouteService; import org.springframework.beans.factory.annotation.Autowired; 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.transaction.annotation.Transactional; import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Mono; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @Service @Transactional public class DynamicRouteService implements ApplicationEventPublisherAware , ApplicationRunner { private final RouteDefinitionWriter routeDefinitionWriter; //路由信息持久化业务层,无需关注 @Autowired private IRouteService routeService; //通过构造方法进行注入,此处通过跟踪代码,RouteDefinitionWriter的实现类是基于内存的,非redis的 private ApplicationEventPublisher publisher; public DynamicRouteService(RouteDefinitionWriter routeDefinitionWriter) { this.routeDefinitionWriter = routeDefinitionWriter; } @Override public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } /** * 增加路由 * * @param routeForm * @return */ public AjaxResult add(CustomRouteDefinition 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 AjaxResult.success(true); } /** * 更新路由 * * @param routeForm * @return */ public AjaxResult update(CustomRouteDefinition routeForm) { RouteDefinition definition = convert(routeForm); try { this.routeDefinitionWriter.delete(Mono.just(definition.getId())).subscribe(); } catch (Exception e) { return AjaxResult.error("未知路由信息",500); } try { routeDefinitionWriter.save(Mono.just(definition)).subscribe(); enduranceRule(routeForm.getName(), routeForm.getDescription(), definition); publishRouteEvent(); return AjaxResult.success(true); } catch (Exception e) { return AjaxResult.error("路由信息修改失败!",500); } } /** * 删除路由 * * @param id * @return */ public AjaxResult delete(String id) { this.routeDefinitionWriter.delete(Mono.just(id)).subscribe(); routeService.deleteByRouteId(id); publishRouteEvent(); return AjaxResult.success(); } private void publishRouteEvent() { this.publisher.publishEvent(new RefreshRoutesEvent(this)); } /** * 刷新路由信息 **/ public AjaxResult flushRoute() { publishRouteEvent(); return AjaxResult.success(true); } /** * 获取路由信息列表 * @return */ public ListgetRouteList(){ List list = routeService.selectList(); List routeList = new ArrayList<>(); if(list != null && !list.isEmpty()){ for (RouteInfo info:list) { routeList.add(this.convertCustomRouteDefinition(info)); } } return routeList; } public CustomRouteDefinition getRouteById(String routeId){ RouteInfo info = routeService.selectByRouteId(routeId); return this.convertCustomRouteDefinition(info); } /** * 转换为自定义路由 * @param info 路由持久化对象 * @return 自定义路由 */ private CustomRouteDefinition convertCustomRouteDefinition(RouteInfo info){ CustomRouteDefinition routeDefinition = new CustomRouteDefinition(); routeDefinition.setDescription(info.getDescription()); routeDefinition.setFilters(JSON.parseArray(info.getFilters(),FilterDefinition.class)); routeDefinition.setId(info.getRouteId()); routeDefinition.setName(info.getRouteName()); routeDefinition.setOrder(info.getOrderNum()); routeDefinition.setPredicateDefinitions(JSON.parseArray(info.getPredicates(), CustomPredicateDefinition.class)); routeDefinition.setUrl(info.getUrl()); return routeDefinition; } /** * 把自定义请求模型转换为RouteDefinition * * @param form * @return */ private RouteDefinition convert(CustomRouteDefinition form) { RouteDefinition definition = new RouteDefinition(); definition.setId(form.getId()); definition.setOrder(form.getOrder()); //设置断言 List predicateDefinitions = form.getPredicateDefinitions().stream() .distinct().map(predicateInfo -> { PredicateDefinition predicate = new PredicateDefinition(); predicate.setArgs(predicateInfo.getArgs()); predicate.setName(predicateInfo.getName()); return predicate; }).collect(Collectors.toList()); definition.setPredicates(predicateDefinitions); if(form.getFilters() != null) { // 设置过滤 List 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.getUrl().startsWith("http")) { uri = UriComponentsBuilder.fromHttpUrl(form.getUrl()).build().toUri(); } else { uri = URI.create(form.getUrl()); } definition.setUri(uri); return definition; } /** * 持久化至数据库 */ public void enduranceRule(String name, String description, RouteDefinition definition) { String id = definition.getId(); List predicates = definition.getPredicates(); List filters = definition.getFilters(); int order = definition.getOrder(); URI uri = definition.getUri(); RouteInfo routeInfo = new RouteInfo(); routeInfo.setRouteName(name); routeInfo.setRouteId(id); routeInfo.setUrl(uri.toString()); routeInfo.setPredicates(JSON.toJSONString(predicates)); routeInfo.setFilters(JSON.toJSONString(filters)); routeInfo.setDescription(description); routeInfo.setOrderNum(order); RouteInfo one = routeService.selectByRouteId(id); if (one == null) { routeService.add(routeInfo); } else { routeInfo.setId(one.getId()); routeService.update(routeInfo); } } /** * 该方法会在网关启动时,从数据库读取路由信息,并加载至内存中。 */ @Override public void run(ApplicationArguments args) throws Exception { System.out.println("----------加载路由信息Start---------"); List list = routeService.selectList(); if(list != null && !list.isEmpty()){ CustomRouteDefinition definition = null; for (RouteInfo info:list) { System.out.println("----------加载路由“"+info.getRouteName()+"”---------"); definition = convertCustomRouteDefinition(info); RouteDefinition routedefinition = convert(definition); routeDefinitionWriter.save(Mono.just(routedefinition)).subscribe(); } publishRouteEvent(); } System.out.println("----------加载路由信息End---------"); } }
Controller层代码如下:
package com.zhangzz.gateway.route.controller; import com.zhangzz.gateway.domain.CustomRouteDefinition; import com.zhangzz.core.entity.AjaxResult; import com.zhangzz.gateway.route.service.impl.DynamicRouteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/route") public class RouteController { @Autowired private DynamicRouteService service; @PostMapping public AjaxResult addRoute(@RequestBody CustomRouteDefinition routeDefinition){ return service.add(routeDefinition); } @PutMapping public AjaxResult updateRoute(@RequestBody CustomRouteDefinition routeDefinition){ return service.update(routeDefinition); } @DeleteMapping("/{routeId}") public AjaxResult deleteRoute(@PathVariable String routeId){ return service.delete(routeId); } /** * 刷新路由 * @return */ @GetMapping("/flushRoute") public AjaxResult flushRoute(){ return service.flushRoute(); } @GetMapping public AjaxResult getList(){ return AjaxResult.success(service.getRouteList()); } @GetMapping("/routeId/{routeId}") public AjaxResult getByRouteId(@PathVariable String routeId){ return AjaxResult.success(service.getRouteById(routeId)); } }
四、网关的启动类
@SpringBootApplication @EnableDiscoveryClient public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class,args); } }
启动网关服务后,通过postman或apipost等工具添加路由信息,同时路由信息会保存入库,下次服务启动会重新装载。
请求地址(POST):http://localhost/route
请求报文:
{ "id":"test1", "name":"routeTest1", "description":"路由测试1", "order":1, "predicateDefinitions":[ { "name":"Path", "args":{ "_genkey_0":"/b3/**", "_genkey_1":"/b4/**" } } ], "filters":[ { "name":"StripPrefix", "args":{ "_genkey_0":"1" } } ], "url":"lb://content" }
浏览器中访问http://localhost/b3或http://localhost/b4,则会直接访问nacos服务中的content服务