学习gateway网关时,是以产品应用为目的,打算做一个类似于SAAS平台,网关负责统一的鉴权,日志记录,对外屏蔽真实的访问地址。路由信息也不能是写死在配置文件的,必须是提供管理页面可维护的。所以就略过配置文件,直接开启动态路由的实现。
一、gateway动态路由需要的jar包
我的springboot及springCloud版本
org.springframework.boot
spring-boot-dependencies
2.6.11
pom
import
org.springframework.cloud
spring-cloud-dependencies
2021.0.1
pom
import
依赖包
org.springframework.cloud
spring-cloud-starter-gateway
org.springframework.cloud
spring-cloud-starter-loadbalancer
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
二、修改application.yml文件
开启服务与发现,路由的真实地址可设置为“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格式的路由数据保存到数据库。以下代码为维护路由信息的业务层,包含路由信息的增、删、改、刷新等方法。
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 List getRouteList(){
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服务