经过上一篇我们发现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