nacos动态路由配置(二)-微服务启动自动配置网关路由 - Go语言中文社区

nacos动态路由配置(二)-微服务启动自动配置网关路由


经过上一篇我们发现nacos通过配置动态路由routes-api-gateway.yaml配置json,监听动态路由变化即可实现动态路由,非常的银杏化。

那么有的小伙伴发现配置json也比较麻烦,有没有更简单更银杏的办法?

改造上一篇的内容:让其他微服务启动完成后自动配置路由信息,简单粗暴更银杏化

上一篇地址:https://blog.csdn.net/CarryBest/article/details/112985659

先看一下路由需要的几个参数

   - id: service11

     uri: lb://service1

     predicates:

        - Path=/api/service1/**

     filters:

        - StripPrefix=2

需要id(唯一性)、url转发的服务名(我们注册的服务名)、predicates断言匹配原则、filters转发服务时候的过滤器这4个参数。

然后我们再打开nacos配置中心,找到public命名空间下的服务(本项目所有服务都注册到public下的),f12看一下,http://xxx.xxx.xxx.xxx:8848/nacos/v1/ns/catalog/services?hasIpCount=true&withInstances=false&pageNo=1&pageSize=10接口返回了我们所有注册的服务。如果你是在dev或者其他命名空间 只需要在url后添加&namespaceId=相应命名空间即可。

 

{
    "serviceList": [
        {
            "name": "service1",
            "groupName": "DEFAULT_GROUP",
            "clusterCount": 1,
            "ipCount": 1,
            "healthyInstanceCount": 1,
            "triggerFlag": "false"
        },
        {
            "name": "service2",
            "groupName": "DEFAULT_GROUP",
            "clusterCount": 1,
            "ipCount": 1,
            "healthyInstanceCount": 1,
            "triggerFlag": "false"
        }
    ],
    "count": 34
}

经过上面点的分析,可以发现,可以通过接口获取所有的注册服务, 那么能不能在服务启动注册到naocs后,路由自己更新呢?其实是可以的。路由需要4个参数

1:id

    可以使用serviceList返回的服务名

2:uri

  可以使用serviceList返回的服务名,前缀拼上lb://

3:predicates

可以默认- Path=/服务名/**

4:filters

由于上面的断言predicates只有一级,那么这里默认- StripPrefix=1

步骤

1:需要配置到网关的项目(这里的服务不是网关,是指需要进过网关的服务)启动成功后调用自定义方法触发动态路由刷新

2:调用naocs的api获取所有服务

3:解析nacos返回的服务集合,拼接好后当做参数给动态路由

一:新建工程common-dynamic-route

提取动态配置相关类,工程截图如下

二:新建动态路由类

package com.carry.www.dynamic.route.config;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.carry.www.dynamic.route.entity.FilterEntity;
import com.carry.www.dynamic.route.entity.PredicateEntity;
import com.carry.www.dynamic.route.entity.RouteEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.list.PredicatedList;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.*;
import java.util.concurrent.Executor;

/**
 * 从Nacos获取动态路由
 * 实现ApplicationEventPublisherAware发布接口来发布路由更新事件
 */
@Configuration
@Component
@Slf4j
public class DynamicRoutingConfigForApp implements ApplicationEventPublisherAware {

    @Value("${dynamic.route.server-addr}")
    private String serverAddr;

    @Value("${nacos.namespace}")
    private String namespace;

    @Value("${dynamic.route.data-id}")
    private String dataId;

    @Value("${dynamic.route.group}")
    private String groupId;

    // 保存、删除路由服务
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    /**
     * @return
     * @Author carryer
     * @Description 获取nacos配置服务
     * @Date
     * @Param
     **/
    public ConfigService getNacosConfigInfo() throws Exception {
        Properties properties = new Properties();
        properties.setProperty(PropertyKeyConst.SERVER_ADDR, serverAddr);
        properties.setProperty(PropertyKeyConst.NAMESPACE, namespace);
        ConfigService configService = NacosFactory.createConfigService(properties);

        return configService;
    }


    /**
     * @return
     * @Author carryer
     * @Description 刷新路由
     * @Date
     * @Param
     **/
    public void refreshRouting() throws IOException {
        String resultContent = "";
        //http://xxx.xxx.xxx.xxx:8848/nacos/v1/ns/catalog/services?hasIpCount=true&withInstances=false&pageNo=1&pageSize=10
        String url = "http://" + this.serverAddr + "/nacos/v1/ns/catalog/services?hasIpCount=true&withInstances=false&pageNo=1&pageSize=1000";
        List<String> params = new LinkedList<>();

        SelfHttp.HttpResult result = SelfHttp.doGet(url, "UTF-8", params);
        if (result.code == HttpURLConnection.HTTP_OK) {
            resultContent = result.content;
        }
        System.out.println(resultContent);
        if (StringUtils.isNotBlank(resultContent)) {
            JSONObject jsonObject = (JSONObject) JSONObject.parse(resultContent);
            List<Map> listMap = (List<Map>) jsonObject.get("serviceList");
            List<RouteEntity> list = new ArrayList<>();
            for (Map m : listMap) {
                String appName = String.valueOf(m.get("name"));
                RouteEntity routeEntity = new RouteEntity();
                routeEntity.setId(appName);
                routeEntity.setUri("lb://" + appName);

                List<FilterEntity> filters = new ArrayList<>();
                FilterEntity filterEntity = new FilterEntity();
                Map<String, String> args = new HashMap<>();
                args.put("parts", "1");
                filterEntity.setName("StripPrefix");
                filterEntity.setArgs(args);
                routeEntity.setFilters(filters);

                PredicateEntity predicateEntity = new PredicateEntity();
                predicateEntity.setName("Path");
                args.clear();
                args = new HashMap<>();
                args.put("pattern", "/" + appName + "/**");
                predicateEntity.setArgs(args);
                List<PredicateEntity> predicates = new ArrayList<>();
                predicates.add(predicateEntity);
                routeEntity.setPredicates(predicates);

                list.add(routeEntity);
            }

            //获取路由集合
            try {
                //更新路由表
                list.stream().forEach(x -> {
                    try {
                        update(assembleRouteDefinition(x));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            } catch (
                    Exception e) {

            }
        }
    }


    /**
     * @return
     * @Author carryer
     * @Description 路由更新
     * @Date
     * @Param
     **/
    private void update(RouteDefinition routeDefinition) throws Exception {
        //先删除路由
        routeDefinitionWriter.delete(Mono.just(routeDefinition.getId()));
        //再保存路由
        routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
        //发布事件 发布者是RefreshRoutesEvent  事件是刷新路由
        applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
    }

    /**
     * @return
     * @Author carryer
     * @Description 实体信息解析
     * @Date
     * @Param
     **/
    private RouteDefinition assembleRouteDefinition(RouteEntity routeEntity) {
        RouteDefinition definition = new RouteDefinition();
        // ID
        definition.setId(routeEntity.getId());

        // Predicates断言
        List<PredicateDefinition> pdList = new ArrayList<>();
        for (PredicateEntity predicateEntity : routeEntity.getPredicates()) {
            PredicateDefinition predicateDefinition = new PredicateDefinition();
            predicateDefinition.setArgs(predicateEntity.getArgs());
            predicateDefinition.setName(predicateEntity.getName());
            pdList.add(predicateDefinition);
        }
        definition.setPredicates(pdList);

        // Filters过滤器
        List<FilterDefinition> fdList = new ArrayList<>();
        for (FilterEntity filterEntity : routeEntity.getFilters()) {
            FilterDefinition filterDefinition = new FilterDefinition();
            filterDefinition.setArgs(filterEntity.getArgs());
            filterDefinition.setName(filterEntity.getName());
            fdList.add(filterDefinition);
        }
        definition.setFilters(fdList);

        // URI
        URI uri = UriComponentsBuilder.fromUriString(routeEntity.getUri()).build().toUri();
        definition.setUri(uri);

        return definition;
    }
}

三:改造HttpSimpleClient、ServerHttpAgent等源码

简单封装http类

package com.carry.www.dynamic.route.config; /**
 * @Title:
 * @Package
 * @Description:
 * @author carryer
 * @date 2021/1/2515:45
 */

import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.client.config.utils.MD5;
import com.alibaba.nacos.client.utils.ParamUtil;
import com.alibaba.nacos.common.constant.HttpHeaderConsts;
import com.alibaba.nacos.common.utils.IoUtils;
import com.alibaba.nacos.common.utils.UuidUtils;
import com.alibaba.nacos.common.utils.VersionUtils;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * @Author carryer
 * @Description //TODO
 * @Date $ $
 * @Param $
 * @return $
 **/
public class SelfHttp {

    public static HttpResult doGet(String url, String encoding, List<String> paramValues) throws IOException {
        HttpURLConnection conn = null;

        try {
            conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(ParamUtil.getConnectTimeout() > 100 ? ParamUtil.getConnectTimeout() : 100);
            conn.setReadTimeout(5000);
            List<String> newHeaders = getHeaders(url, new ArrayList<>(), paramValues);
            setHeaders(conn, newHeaders, encoding);

            conn.connect();

            int respCode = conn.getResponseCode();
            String resp = null;

            if (HttpURLConnection.HTTP_OK == respCode) {
                resp = IoUtils.toString(conn.getInputStream(), encoding);
            } else {
                resp = IoUtils.toString(conn.getErrorStream(), encoding);
            }
            return new HttpResult(respCode, conn.getHeaderFields(), resp);
        } finally {
            IoUtils.closeQuietly(conn);
        }
    }

    public static List<String> getHeaders(String url, List<String> headers, List<String> paramValues)
            throws IOException {
        List<String> newHeaders = new ArrayList<String>();
        newHeaders.add("exConfigInfo");
        newHeaders.add("true");
        newHeaders.add("RequestId");
        newHeaders.add(UuidUtils.generateUuid());
        if (headers != null) {
            newHeaders.addAll(headers);
        }
        return newHeaders;
    }

    static public void setHeaders(HttpURLConnection conn, List<String> headers, String encoding) {
        if (null != headers) {
            for (Iterator<String> iter = headers.iterator(); iter.hasNext(); ) {
                conn.addRequestProperty(iter.next(), iter.next());
            }
        }
        conn.addRequestProperty(HttpHeaderConsts.CLIENT_VERSION_HEADER, VersionUtils.VERSION);
        conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + encoding);

        String ts = String.valueOf(System.currentTimeMillis());
        String token = MD5.getInstance().getMD5String(ts + ParamUtil.getAppKey());

        conn.addRequestProperty(Constants.CLIENT_APPNAME_HEADER, ParamUtil.getAppName());
        conn.addRequestProperty(Constants.CLIENT_REQUEST_TS_HEADER, ts);
        conn.addRequestProperty(Constants.CLIENT_REQUEST_TOKEN_HEADER, token);
    }

    static public class HttpResult {
        final public int code;
        final public Map<String, List<String>> headers;
        final public String content;

        public HttpResult(int code, String content) {
            this.code = code;
            this.headers = null;
            this.content = content;
        }

        public HttpResult(int code, Map<String, List<String>> headers, String content) {
            this.code = code;
            this.headers = headers;
            this.content = content;
        }
    }
}

 

 

四:其他服务如何使用

1:其他服务把动态路由工程引入当前工程

<dependency>
    <groupId>com.carry.www</groupId>
    <artifactId>common-dynamic-route</artifactId>
    <version>1.0.0</version>
</dependency>

2:然后在启动类启动成功后调用动态路由刷新

@Autowired
DynamicRoutingConfigForApp dynamicRoutingConfigForApp;
@Override
public void run(String... args) throws Exception {
    System.out.println("###################### 订单 服务启动完成!######################");
    System.out.println("###################### 自动注册路由 START!######################");
    dynamicRoutingConfigForApp.refreshRouting();
    System.out.println("###################### 自动注册路由 END!######################");
}

 

五:测试

为了方便直接使用网关测试,启动网关类,在网关启动成功的时候,也触发了动态路由的加载。

输入地址,使用网关地址+网关端口+配置到网关的服务名+方法名进行访问,http://localhost:9998/api-gatway/defaultfallback,api-gatway是我们注册的服务名也是路由的断言,成功进入断点,代表上面路由已经更新。

相关源码地址:https://github.com/tomducky/hdys-supporter

 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/CarryBest/article/details/113114079
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-05-30 00:29:28
  • 阅读 ( 1130 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢