最近项目中有一个需求:由于有多个服务需要接入系统,并且后续会越来越多服务接入,此时外部系统调用这些服务会比较麻烦,不同的端口,不同的路径,并且权限不好管理。此时就想到了将对这些服务的请求统一通过网关转发,统一生成一个路由前缀,并且转发前统一在网关进行鉴权。当这些服务注册到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