ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Spring Boot Cloud 的动态路由模块

2019-09-06 17:41:07  阅读:140  来源: 互联网

标签:Spring route Boot gateway id import com public Cloud


刚刚上班,第一个项目是前辈写的公司内部的服务平台,我们进行二期的功能完善,一开始啥也不会,就只会增删改查,看见动态路由,什么都不懂,就只能百度,之后问老师傅,然后慢慢写,下面这个是我写完之后的动态路由的,希望能给和我一样的萌新一点启发。

 

首先,要有三个model类,分别是route (路由类),pedicate(断言类)还有filter(过滤器类),这三个类是我要存进数据库中需要的类。

mport com.baomidou.mybatisplus.annotations.TableField;
import lombok.Data;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Data
public class Route  {


    //路由ID
    private String route_id;
    //路由断言
    private String predicate;
    //路由断言集合配置
    @TableField(exist = false)
    private List<Predicate> predicates = new ArrayList<>();
    //路由过滤器
    private String filter_code;
    //路由过滤器集合配置
    @TableField(exist = false)
    private List<Filter> filters = new ArrayList<>();
    //路由规则转发的目标uri
    private String uri;
    //路由执行的顺序
    public int order = 0;
    //备注
    public String remark;
    //创建时间

    public Date create_date;


}

 

import lombok.Data;

import java.util.LinkedHashMap;
import java.util.Map;
@Data
public class Predicate {
    /**
     * 断言名称
     */
    private String name;
    /**
     * 断言规则
     */
    private Map<String,String> args = new LinkedHashMap<>();

}
import lombok.Data;

import java.util.LinkedHashMap;
import java.util.Map;

@Data
public class Filter {


    /**
     * 过滤器名称
     */
    private String name;

    /**
     * 过滤规则
     */
    private Map<String,String> args = new LinkedHashMap<>();
}

之后,因为使用了mybatis plus 所以我有一个 DAO,也就是RouteDao,里面只有一个findlist方法,这里就不写出来了。

题外话,要是你要分页的话,一定要写一下mp的分页配置,这个坑我先踩为敬~

之后就是我们的Service还有Impl,

Service这边一共有两个,分别是DynamicRouteService(动态路由)还有数据库的RouteSQLService(数据库路由),其中动态路由是实际上使用的路由服务的,也就是增删改查这些东西,数据库,大家都懂的。

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.cloud.gateway.support.NotFoundException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;




@Service
public class DynamicRouteService implements ApplicationEventPublisherAware {

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    private ApplicationEventPublisher publisher;


    //增加路由
    public String add(RouteDefinition definition) {


        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        notifyChanged();


        return "success";

    }

    //更新路由
    public String update(RouteDefinition definition) {
        try {

            delete(definition.getId()).subscribe();
        } catch (Exception e) {
            return "update fail,not find route  routeId: " + definition.getId();
        }
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            notifyChanged();
            return "success";
        } catch (Exception e) {
            return "update route  fail";
        }
    }

    //删除路由
    public Mono<ResponseEntity<Object>> delete(String id) {

        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())).subscribe();

        return null;

    }


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

    public void notifyChanged() {
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }

这其中我踩了一些坑,比如说删除路由最后的一个方法.subscribe(),这个是必要的不能省略,而我找到的无论哪个博客,还有说明文档,上面都没有这个,我也不知道是本来就不用,还是他们忘了写,反正人家能成功,我就不行,这个bug我找了很久,打断点也进不去,最后比较和添加的差别,发现了这个,有点难受。

SQL的Impl我也放进来,给大家看一下,



import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.xctech.common.api.JSONResult;
import com.xctech.gateway.common.utils.Query;
import com.xctech.gateway.dao.RouteDao;
import com.xctech.gateway.model.Route;
import com.xctech.gateway.service.RouteSQLService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Map;

@Service
public class RouteSQLServiceImpl extends ServiceImpl<RouteDao, Route> implements RouteSQLService {

    @Autowired
    RouteDao routeDao;


    @Override
    public JSONResult<Object> save(Route route) {
        //判断是否已经存在该路由的ip
        EntityWrapper<Route> wrapper = new EntityWrapper<>();

        wrapper.eq("route_id", route.getRoute_id());

        int flag = this.selectCount(wrapper);
        if (flag>0) {
            return JSONResult.error();
        }
        //理论上还要一个用户限判定的,现在没有。
        route.setCreate_date(new Date());
        return JSONResult.success(this.insert(route));


    }

    @Override
    public JSONResult<Object> remove(String route_id) {
        //判断route_id是否为空

        EntityWrapper<Route> wrapper = new EntityWrapper();
        wrapper.eq("route_id",route_id);
        int flag = this.selectCount(wrapper);
        if (flag==0) {
            return JSONResult.error();
        }
        return JSONResult.success(routeDao.delete(wrapper));

    }

    @Override
    public JSONResult<Object> update(Route route) {

       //判断route_id是否为空

        String old_id = route.getRoute_id();
        EntityWrapper<Route> wrapper = new EntityWrapper();
        wrapper.eq("route_id",old_id);
        int flag = this.selectCount(wrapper);
        if (flag==0) {
            return JSONResult.error();
        }
//        System.out.println(route.getUri());
        return JSONResult.success(routeDao.update(route,wrapper));

    }

    @Override
    public JSONResult<Route> findPage(Map<String,Object> param) {

        Page<Route> p = new Query<Route>(param).getPage();
        p.setRecords(routeDao.findList(p, param));
        return JSONResult.success(p);


        }

    @Override
    public List<Route> findList() {

        return this.selectList(new EntityWrapper<Route>());

    }

    @Override
    public JSONResult<Route> select(String route_id) {
        EntityWrapper<Route> entityWrapper = new EntityWrapper<>();

        entityWrapper.like("route_id", route_id);


        return JSONResult.success(this.selectList(entityWrapper));
    }
}

这个没什么新意。。。而且我写代码比较乱,最近在该我的代码风格,大家看看就行~~

这些都有了之后,其实我们已经快写完了,再有一个controller就好,这里我把我的controller放给大家

package com.xctech.gateway.controller;


import com.xctech.common.api.JSONResult;
import com.xctech.gateway.common.utils.HttpServletReq;
import com.xctech.gateway.common.utils.PackUntil;
import com.xctech.gateway.common.utils.UnPackUntil;

import com.xctech.gateway.model.Route;
import com.xctech.gateway.service.DynamicRouteService;
import com.xctech.gateway.service.RouteSQLService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import reactor.ipc.netty.http.server.HttpServerRequest;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

@RestController
@RequestMapping("/route")
public class RouteController {

    @Autowired
    RouteSQLService routeSQLService;
    @Autowired
    UnPackUntil unPackUntil;
    @Autowired
    PackUntil packUntil;
    @Autowired
    DynamicRouteService dynamicRouteService;



    @PostMapping("/add")
    @ResponseBody

    public JSONResult<Object> save(@RequestBody Route route) {


        dynamicRouteService.add(packUntil.routeDefindition(route));


        return routeSQLService.save(route);
    }

    @PutMapping("/update")
    @ResponseBody
    public JSONResult<Object> update(@RequestBody Route route) {

        dynamicRouteService.update(packUntil.routeDefindition(route));
        return routeSQLService.update(route);

    }
    @PostMapping("/{route_id}")
    public JSONResult<Route> find(@PathVariable String route_id) {
        return routeSQLService.select(route_id);
    }


//    @PostMapping("find/{route_id}")
//    public JSONResult<Route> findlike(@PathVariable String route_id) {
//        return routeSQLService.findPage(route_id);
//    }

//    @GetMapping("/findPage")
//    public JSONResult<Object> findPage(HttpServletRequest request) {
//        return JSONResult.success(routeSQLService.findPage(HttpServletReq.getParamMap(request)));
//}



    @DeleteMapping("/delete/{route_id}")
    public  JSONResult<Object> remove(@PathVariable String route_id) {
        dynamicRouteService.delete(route_id);

        return routeSQLService.remove(route_id);
    }

    @GetMapping("/findList")
    public JSONResult<List<Route>> findList() {
        return JSONResult.success(routeSQLService.findList());
    }

    @PostMapping("/reflesh")

    public JSONResult reflesh() {
        dynamicRouteService.notifyChanged();
        return JSONResult.success();
    }

}

大家可能注意到了,在传参的时候,前端的小伙伴给我穿的是我数据库的实体,但是实际路由使用时,传的是

RouteDefinition  PredicateDefinition  FilterDefinition  这三个东西

他们来自于

import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;

这三个包,导入的时候别弄错了。

为了将我的三个自定义实体转换成为实际要用的三个,我还做了一个包装的工具类

import com.xctech.gateway.model.Filter;
import com.xctech.gateway.model.Predicate;
import com.xctech.gateway.model.Route;
import org.springframework.beans.factory.annotation.Autowired;
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.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@Component
public class PackUntil {


    @Autowired
    UnPackUntil unPackUntil;
    FilterDefinition filter = new FilterDefinition();
    PredicateDefinition predicate = new PredicateDefinition();


    public  RouteDefinition routeDefindition(Route route) {

        RouteDefinition realRoute = new RouteDefinition();

//        List<Route> messageList = unPackUntil.getRouteMessage(packJson);

        //设置route内容
        realRoute.setId(route.getRoute_id());
        realRoute.setOrder(route.getOrder());

        //解包 filter

        String filterall = route.getFilter_code();//得到 filterCode,为下一步解包做准备

        List<Filter> filterList =unPackUntil.getFilterList(filterall);//得到存有filter全部信息的list
        FilterDefinition filterDefinition = new FilterDefinition();

        for (int i = 0; i <filterList.size() ; i++) {
//            filter.setArgs(filterList.get(i).getArgs());

            filterDefinition.setArgs(filterList.get(i).getArgs());
            filterDefinition.setName(filterList.get(i).getName());

    }
        List<FilterDefinition> filters = new ArrayList();
        filters.add(filterDefinition);
        realRoute.setFilters(filters);





        //解包 Pre
        String preAll = route.getPredicate();//得到 filterCode,为下一步解包做准备

        List<Predicate> preList =unPackUntil.getPreList(preAll);//得到存有p全部信息的list

        for (int i = 0; i <preList.size(); i++) {
            predicate.setArgs(preList.get(i).getArgs());
            predicate.setName(preList.get(i).getName());

        }
        List<PredicateDefinition> predicateList = new LinkedList<>();

        predicateList.add(predicate);

        realRoute.setPredicates(predicateList);






        //uri的设置
        URI uri = null;
        if(route.getUri().startsWith("http")){
            uri = UriComponentsBuilder.fromHttpUrl(route.getUri()).build().toUri();
        }else{
            uri = URI.create(route.getUri());
        }
        realRoute.setUri(uri);


        return realRoute;  }



}

同时,前端传过来的route实体,我们这里也要转换成JSON来进行数据库的使用,所以有些了解包的工具类

import com.alibaba.fastjson.JSONObject;
import com.xctech.gateway.model.Filter;
import com.xctech.gateway.model.Predicate;
import com.xctech.gateway.model.Route;
import org.springframework.stereotype.Component;

import java.lang.reflect.ParameterizedType;
import java.util.*;

@Component
public class UnPackUntil {




    //用于解包,将前端给我们的JSON转换成我们需要的单独的字符串形式,并放入集合//

    public List getRouteMessage(String str) {

        List<Route> routeMList = JSONObject.parseArray(str, Route.class);


        return routeMList;

    }


    public List getFilterList(String str) {

        List<Filter> filterlist = JSONObject.parseArray(str, Filter.class);



        return filterlist;

    }

    public List<Predicate> getPreList(String str) {

        List<Predicate> preList = JSONObject.parseArray(str, Predicate.class);



        return preList;

    }}

 

这样功能就都完成了,

然后,老师傅跟我说应该有一个服务启动时,将sql中的路由自动填进去的处理,我不知道怎么实现启动时进行调用,索性就加在启动类里面了,做了个初始化的类,之后启动类调用

 

package com.xctech.gateway;

import com.xctech.gateway.common.utils.PackUntil;
import com.xctech.gateway.model.Route;
import com.xctech.gateway.service.DynamicRouteService;
import com.xctech.gateway.service.RouteSQLService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.LinkedList;
import java.util.List;

@Component
public class InitRoute {
    @Autowired
    RouteSQLService sqlService;

    @Autowired
    PackUntil packUntil;

    @Autowired
    DynamicRouteService dynamicRouteService;

    private static InitRoute  initRoute ;//静态初始化当前类

    @PostConstruct //通过@PostConstruct实现初始化bean之前进行的操作

    public void init() {
        initRoute = this;
        initRoute.sqlService = this.sqlService;
        initRoute.packUntil = this.packUntil;
        initRoute.dynamicRouteService = this.dynamicRouteService;

    }
    List<Route> list = new LinkedList<>();


    public List<Route> getList() {

        List<Route> routeList = initRoute.sqlService.findList();
        list = routeList;
        return list;

    }

    public String initRouteList(List<Route> list) {

        for (int i = 0; i <list.size() ; i++) {

        RouteDefinition definition = initRoute.packUntil.routeDefindition(list.get(i));

        initRoute.dynamicRouteService.add(definition);

    }

        return "success";
    }

}

就完事了,就这些东西,从无到有,我写了一个多星期。

接下来就可以到动态路由验证的uri验证一下http://127.0.0.1:8762/actuator/gateway/routes也就是这个

之后你添加一条,如果成功添加,就会有相应的出现。

最后的最后,注意的是添加进去的时候,JSON中有的字段,是不能随便填的,我给你的例子是

{
            "route_id": "file_service",
            "predicate": "[{'args':{'_genkey_0':'/system/file/**'},'name':'Path'}]",
            "filter_code": "[{'args':{'_genkey_0':'2'},'name':'StripPrefix'}]",
            "uri": "lb://FILE-SERVICE",
            "order": 1,
            "remark": "4444444444"
        }

这样一个JSON,其中_genkey_0 是有固定规则的,Path , StripPrefix 这两个是有几个固定的值的,随便写不行,/system/file/**

是你的实际地址,'_genkey_0':'2' 是指你过滤了/system/file/**  中的前两个,路由转换后,你的另一个服务就只能得到/**这个url了,大概就是这些。这个模块就完成了。

希望对大家有帮助~~~

标签:Spring,route,Boot,gateway,id,import,com,public,Cloud
来源: https://blog.csdn.net/qq_37963940/article/details/100582143

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有