最近項目中有一個需求:由於有多個服務需要接入系統,並且後續會越來越多服務接入,此時外部系統調用這些服務會比較麻煩,不同的端口,不同的路徑,並且權限不好管理。此時就想到了將對這些服務的請求統一通過網關轉發,統一生成一個路由前綴,並且轉發前統一在網關進行鑑權。當這些服務註冊到eureka時,動態的生成服務的路由規則,避免在配置文件中手動配置。
創建一個類繼承SimpleRouteLocator,實現RefreshableRouteLocator接口:
import com.netflix.appinfo.InstanceInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient;
import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 自定義適配器服務路由規則
*
* @author hsz
*/
@Slf4j
public class AdapterRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
@Autowired
private DiscoveryClient discoveryClient;
public AdapterRouteLocator(String servletPath, ZuulProperties properties) {
super(servletPath, properties);
}
@Override
public void refresh() {
doRefresh();
}
@Override
protected Map<String, ZuulRoute> locateRoutes() {
//重新定義一個路由映射map
LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
//獲取所有服務
List<String> services = discoveryClient.getServices();
//設置適配器路由規則
for (String service : services) {
List<ServiceInstance> serviceInstances = discoveryClient.getInstances(service);
serviceInstances.forEach(serviceInstance -> {
EurekaDiscoveryClient.EurekaServiceInstance instance = (EurekaDiscoveryClient.EurekaServiceInstance) serviceInstance;
InstanceInfo instanceInfo = instance.getInstanceInfo();
if ("ADAPTER-SERVICE".equals(instanceInfo.getAppGroupName())) {
ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();
zuulRoute.setId(instanceInfo.getAppName().toLowerCase());
zuulRoute.setUrl(serviceInstance.getUri().toString());
zuulRoute.setRetryable(false);
String path = "/adapter/" + instanceInfo.getAppName().toLowerCase() + "/**";
zuulRoute.setPath(path);
routesMap.put(path, zuulRoute);
}
});
}
return routesMap;
}
}
主要的邏輯都在locateRoutes()方法中。首先定義一個路由映射map,用來存放路由規則。通過DiscoveryClient獲取到服務註冊列表,將組名爲“ADAPTER-SERVICE”的適配器服務過濾出來解析並構造路由放入到map中,最後這個routesMap就是自定義的路由規則。
Zuul在自動配置加載時會默認注入了2個自帶RouteLocator:
CompositeRouteLocator:包含了其他所有的RouteLocator,匹配路徑時也是通過這個類的getMatchingRoute方法依次調用其他RouteLocator的getMatchingRoute方法類匹配,只要匹配到第一個就返回,不會再往下匹配;
DiscoveryClientRouteLocator:這個路由器包括兩部分規則,一個是加載的父類SimpleRouteLocator中的規則,即配置文件中的路由配置;一個是根據服務註冊列表生成的,匹配路徑是“/服務名/**”,如果配置了ignored-services: ‘*’,則不會生成這個規則。並且如果有匹配路徑是“/**”的,則將它移到最後面。
PreDecorationFilter前置過濾器通過CompositeRouteLocator來獲取匹配的路由。
最後用配置類使這個路由規則生效
import com.fdjw.msgateway.route.AdapterRouteLocator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author hsz
*/
@Configuration
public class RouteConfig {
private final ZuulProperties zuulProperties;
private final ServerProperties server;
@Autowired
public RouteConfig(ZuulProperties zuulProperties, ServerProperties server) {
this.zuulProperties = zuulProperties;
this.server = server;
}
@Bean
public AdapterRouteLocator getRouteLocator() {
return new AdapterRouteLocator(server.getServlet().getContextPath(), zuulProperties);
}
}
springboot版本:2.1.3