The first two, mainly to write direct operating entity classes and tables
Next, the main instructions to configure the gateway of dynamic loading.
By analyzing the spring Cloud gateway source can be found, the default implementation class is RouteDefinitionWriter InMemoryRouteDefinitionRepository, as shown:
RouteDefinitionRepository 继承了RouteDefinitionWriter,是 Spring Cloud Gateway官方预留的接口。从而可以通过下面两种方式来实现集群下的动态路由修改:RouteDefinitionWriter 接口和 RouteDefinitionRepository 接口。在这里推荐实现RouteDefinitionRepository 这个接口,从数据库或者从配置中心获取路由进行动态配置。
Here affixed directly to my mysql implementation class: MysqlRouteDefinitionRepository
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.keda.gateway.entity.DynamicRouteVo;
import com.keda.gateway.entity.GatewayDynamicRoute;
import com.keda.gateway.service.IGatewayDynamicRouteService;
import com.keda.gateway.struts.DynamicRouteStruts;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @Author: seowen
* @Date: 2019/12/19 15:27
* @Version 1.0
*/
@Component
public class MysqlRouteDefinitionRepository implements RouteDefinitionRepository {
@Autowired
private IGatewayDynamicRouteService routeService;
/**
* Gateway启动的时候,会加载这个方法
* @return
*/
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
//从数据库中,获取我们自定义的 路由信息数据
List<GatewayDynamicRoute> listByEnable = routeService.getListByEnable(true);
if (CollectionUtils.isNotEmpty(listByEnable)){
//转换成 RouteDefinition 集合后,返回
return Flux.fromIterable(this.toRouteList(listByEnable));
}
//如果 数据库为 空,则返回一个 空的集合
return Flux.fromIterable(new ArrayList<RouteDefinition>());
}
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return null;
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
return null;
}
/**
* 转换成 List<RouteDefinition>
* @param listByEnable
* @return
*/
private List<RouteDefinition> toRouteList(List<GatewayDynamicRoute> listByEnable){
List<RouteDefinition> routeList = new ArrayList<>();
/**
* 循环转换:
* 因为数据库中,Predicates 和 Filters 存储的 json字符串。所以,得先转换成 对应的 vo.
* 然后在转换成 List<PredicateDefinition>和 List<FilterDefinition>
*/
listByEnable.stream().forEach(gw->{
RouteDefinition r = new RouteDefinition();
r.setUri(DynamicUtil.getUri(gw.getUri()));
r.setOrder(gw.getOrder());
r.setId(gw.getRouteId());
r.setPredicates(DynamicRouteStruts.INSTANCES.toPredicateDefinition(JSONArray.parseArray(gw.getPredicates(), DynamicRouteVo.PredicateDefinitionVo.class)));
r.setFilters(DynamicRouteStruts.INSTANCES.toFilterDefinition(JSONArray.parseArray(gw.getFilters(),DynamicRouteVo.FilterDefinitionVo.class)));
routeList.add(r);
});
return routeList;
}
}
MysqlRouteDefinitionRepository 类写好后,还不行。 我们需要当每次操作 动态有路由表,都希望 gateway网关,重新加载 路由数据。 这个时候,我们就需要一个spring事件来支持。
DynamicRouteServiceImpl 类,就是我的 事件事件
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
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.stereotype.Service;
import reactor.core.publisher.Mono;
/**
* @Author: seowen
* @Date: 2019/12/19 15:49
* @Version 1.0
*/
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
/**
* 增加路由
* @param routeDefinition
* @return
*/
public String add(RouteDefinition routeDefinition){
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
this.doLoad();
return "success";
}
/**
* 更新路由
*/
public String update(RouteDefinition definition) {
try {
this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
} catch (Exception e) {
return "update fail,not find route routeId: " + definition.getId();
}
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.doLoad();
return "success";
} catch (Exception e) {
return "update route fail";
}
}
/**
* 删除路由
*
*/
public String delete(String id) {
// return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(()->Mono.just(ResponseEntity.ok().build())))
// .onErrorResume(t -> t instanceof NotFoundException,t -> Mono.just(ResponseEntity.notFound().build()));
try {
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
this.doLoad();
} catch (Exception e) {
e.printStackTrace();
return "delete fail,not find route routeId: " + id;
}
return "delete success";
}
/**
* 重新刷新 路由
*/
public String doLoad() {
try {
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}catch (Exception e){
e.printStackTrace();
return "load fail";
}
return "load success";
}
}
We then after each database operation, you need to call this doLoad () method to refresh the routing:
as the add method GatewayDynamicRouteController
/**
* 增加路由
* @param routeBean
* @return
*/
@PostMapping("/add")
public String add(@RequestBody DynamicRouteVo.RouteBean routeBean){
service.saveOne(DynamicRouteStruts.INSTANCES.toGatewayDynamicRoute(routeBean));
return this.dynamicRouteService.doLoad();
}
Special Note:
{
"routeId":"keda-creditcard",
"uri":"lb://KEDA-CREDITCARD",
"order":"1",
"enable":"true",
"predicates":[
{
"name": "Path",
"args": {
"_genkey_0": "/main/**"
}
}
],
"filters":[
{
"name": "StripPrefix",
"args": {
"_genkey_0": "1"
}
},
{
"name": "Retry",
"args": {
"retries": "3",
"series.0": "SERVER_ERROR",
"methods.0": "GET",
"methods.1": "POST"
}
}
]
}
Above, the request parameters:
When routing information. Typically name = value form.
The above Path and StripPrefix is the common name attribute name . Of particular note is a value corresponding to, "/ main / **" and "1" . When gateway static route configuration, we will directly yml file, routing information, as shown
when the predicates and filters at the node configuration is a simple name = value time. When loading the gateway, which will press the "=" (i.e. char 61) is divided. Then the value to "," is divided into a String array . Then, loop through the array to " _genkey_ " prefix + array subscript after, is key. Value to value.
To keep in args Map
Source as follows:
Therefore, when we add routes, but also to comply with this rule. Such as:
"name": "Path",
"args": {
"_genkey_0": "/main/**"
}
And, when the route is added, there is args properties, and to the corresponding key: value pairs when, if the key is a complex (i.e., collection).
That corresponding key will be "key. Small sign" to represent. Such as:
"name": "Retry",
"args": {
"retries": "3",
"series.0": "SERVER_ERROR",
"methods.0": "GET",
"methods.1": "POST"
}