欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

springcloud-zuul根据请求url动态更新路由表

程序员文章站 2022-04-15 18:58:46
由于公司环境较多,为避免频繁更新网关路由表配置,同时方便测试时可以略过网关解密功能,需要根据固定规则的请求url自动生成路由表,并转发请求到后端服务。要求:支持注册中心服务名与固定ip转发请求。兼容配置文件中已经配置的路由转发规则只有配置文件中允许的模块才允许动态路由不允许重复创建并刷新路由,防止请求过慢实际测试来看,如果请求走动态创建路由规则后再转发请求到后端接口,则请求要慢15倍左右。1. 创建过滤器拦截所有请求url并判断是否需要创建动态路由@Componentpublic cl...

由于公司环境较多,为避免频繁更新网关路由表配置,同时方便测试时可以略过网关解密功能,需要根据固定规则的请求url自动生成路由表,并转发请求到后端服务。
要求:

  1. 支持注册中心服务名与固定ip转发请求。
  2. 兼容配置文件中已经配置的路由转发规则
  3. 只有配置文件中允许的模块才允许动态路由
  4. 不允许重复创建并刷新路由,防止请求过慢

实际测试来看,如果请求走动态创建路由规则后再转发请求到后端接口,则请求要慢15倍左右。

1. 创建过滤器拦截所有请求url并判断是否需要创建动态路由

@Component
public class DynamicRouteFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(DynamicRouteFilter.class);

    @Autowired
    private DynamicRouteLocator dynamicRouteLocator;

    //允许动态路由的模块名
    @Value("${dynamic-route-module}")
    private String moduleName;

    // 允许动态路由的模块名集合
    private static LinkedHashSet<String> moduleNames = new LinkedHashSet<>();

    // 健康检查路径
    private final static String HEALTH_URL = "/health";

    private final static String BACKSLASH  = "/";


    /**
     * 加载配置文件中允许动态路由的模块名
     *
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Collections.addAll(moduleNames, moduleName.split(","));
    }

    /**
     * 获取请求路径,判断是否需要创建路由
     *
     * @param servletRequest
     * @param servletResponse
     * @param filterChain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String requestURI = request.getRequestURI(); // /apaas/api/user-service/getalluser
        if (BACKSLASH.equals(requestURI)){
            logger.error("request url is blank");
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        String[] urls = requestURI.split(BACKSLASH);
        if (requestURI.endsWith(HEALTH_URL) ||
                (StringUtils.isNotBlank(urls[1]) && !moduleNames.contains(urls[1]))) {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        StringBuffer result = new StringBuffer(64);
        boolean notBlank = true;
        for (int i = 1; i < 4; i++) {
            notBlank = notBlank && StringUtils.isNotBlank(urls[i]);
        }
        if (notBlank) {
            result.append("/").append(urls[1]).append("/").append(urls[2]).append("/")
                    .append(urls[3]).append("/**");
        }
        if (this.create(result.toString())) {
            filterChain.doFilter(servletRequest, servletResponse);
        }
    }

    @Override
    public void destroy() {
    }

    /**
     * 如果路由表中没有,则创建动态路由
     *
     * @param result
     * @return
     */
    private synchronized boolean create(String result) {
        Map<String, ZuulProperties.ZuulRoute> routeMap = dynamicRouteLocator.getRouteMap();
        if (!routeMap.containsKey(result)) {
            dynamicRouteLocator.getUrl(result);
        }
        return true;
    }
}

2. spring-cloud-zuul要实现自定义路由需要继承SimpleRouteLocator类并实现路由刷新接口RefreshableRouteLocator

public class DynamicRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
    private static final Logger logger = LoggerFactory.getLogger(DynamicRouteLocator.class);
    @Autowired
    private ZuulHandlerMapping zuulHandlerMapping;

    // 初始化路由规则map长度
    private final static int mapSize = 1024;

    // 初始化路由规则id长度
    private final static int routeIdSize = 64;

    // 路由规则map
    private static Map<String, ZuulProperties.ZuulRoute> routeMap;

    //路由转发路径
    private String url = null;

    /**
     * 加载配置文件中的路由配置并且创建新路由表
     *
     * @param servletPath
     * @param properties
     */
    public DynamicRouteLocator(String servletPath, ZuulProperties properties) {
        super(servletPath, properties);
        routeMap = new HashMap<>(mapSize);
        routeMap.putAll(super.locateRoutes());
        logger.info("load " + super.locateRoutes().size() + " Routes rule from yml");
    }

    /**
     * 获取请求url,刷新路由
     *
     * @param url
     */
    public void getUrl(String url) {
        this.url = url;
        this.refresh();
    }

    /**
     * 获取请求url,刷新路由
     *
     * @return
     */
    public Map<String, ZuulProperties.ZuulRoute> getRouteMap() {
        return locateRoutes();
    }

    /**
     * 拼接信息并创建路由刷新规则
     *
     * @return 路由规则表
     */
    @Override
    protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {
        try {
            String path = this.url;// /apaas/api/mp-b-configure-service/**
            if (StringUtils.isBlank(path)) {
                return routeMap;
            }
            String[] urls = path.split("/");
            if (urls.length < 5) {
                logger.error("urls length not match");
                return routeMap;
            }
            String target = "";
            ZuulProperties.ZuulRoute zuulRoute = null;
            StringBuilder id = new StringBuilder(routeIdSize);
            id.append(urls[1]).append("-").append(urls[2]).append("-").append(urls[3]);
            if (urls[3].startsWith("ip_")) {
                String[] ipAndPort = urls[3].split("_");
                target = "http://" + ipAndPort[1] + ":" + ipAndPort[2];
                // 生成转发到固定ip port的ZuulRoute对象
                zuulRoute = createZuulRouteWithUrl(id.toString(), path, target);
            } else {
                target = urls[3];
                // 生成转发到服务名的ZuulRoute对象
                zuulRoute = createZuulRouteWithServiceID(id.toString(), path, target);
            }
            routeMap.put(path, zuulRoute);
            this.url = null;
            // 刷新路由,添加到SpringMvc的HandlerMapping链中,只有选择了ZuulHandlerMapping的请求才能出发到Zuul的后续流程
            zuulHandlerMapping.setDirty(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return routeMap;
    }

    @Override
    public void refresh() {
        locateRoutes();
    }

    /**
     * 创建路由规则
     *
     * @param id        路由表唯一id
     * @param path      映射路径
     * @param serviceId 服务名
     */
    private ZuulProperties.ZuulRoute createZuulRouteWithServiceID(String id, String path, String serviceId) {
        ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();
        zuulRoute.setId(id);
        zuulRoute.setPath(path);
        zuulRoute.setServiceId(serviceId);
        zuulRoute.setStripPrefix(true);
        logger.info("创建了路由 " + zuulRoute.toString());
        return zuulRoute;
    }

    /**
     * 创建路由规则
     *
     * @param id   路由表唯一id
     * @param path 映射路径
     * @param url  固定ip:port
     */
    private ZuulProperties.ZuulRoute createZuulRouteWithUrl(String id, String path, String url) {
        ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();
        zuulRoute.setId(id);
        zuulRoute.setPath(path);
        zuulRoute.setStripPrefix(true);
        zuulRoute.setUrl(url);
        logger.info("创建了路由 " + zuulRoute.toString());
        return zuulRoute;
    }
}

3. 启动类加载自定义路由bean
ZuulProperties springboot已经提前加载,直接使用即可

    @Bean
    public DynamicRouteLocator getRouteLocator(ZuulProperties zuulProperties){
        return new DynamicRouteLocator(zuulProperties.getPrefix(), zuulProperties);
    }

4. 网关路由规则

  1. Yml配置文件配置静态路由
  • 例如:
    sso:
    path: /api/sso/**
    service-id: mp-b-sso-service
  1. 动态路由
    服务yml中配置允许动态路由的模块名称,多个模块使用,分割,只有配置了允许动态路由的模块才能走动态路由,不配置不允许走动态路由。
    动态路由请求url规则:
  • 例如:
    http://localhost:9099/apaas/api/mp-b-user-service/getall
    http://localhost:9099/apaas/openapi/mp-b-user-service/getall
    /apaas:模块名,类似apaas,pay,等等
    /api:需要解密的请求
    /openapi:不需要解密的请求
    /mp-b-user-service:请求的后端服务在注册中心的名称
    /getall:后端服务接口
  • 例如:
    http://localhost:9099/apaas/api/ip_127.0.0.1_9090/getall
    http://localhost:9099/apaas/openapi/ip_127.0.0.1_9090/getall
    按照规则的url可选择是否使用加密请求后端固定ip端口服务,无需再配置文件中添加路由
    url规则:http://ip:port/模块名/是否要解密/ip_服务ip_服务端口/后端服务接口
    /apaas:模块名,类似apaas,pay,等等
    /api:需要解密的请求
    /openapi:不需要解密的请求
    ip_127.0.0.1_9090:请求的后端服务的ip端口,注意是ip_为开始标志
    /getall:后端服务接口

本文地址:https://blog.csdn.net/weixin_43908525/article/details/109643186

相关标签: springcloud java