SpringCloud-Gateway配置及持久化、过滤器、异常处理

24 篇文章 0 订阅
15 篇文章 0 订阅


gateway不能和web一起使用 需要排除掉

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

解决冲突

yml配置

spring:
  main:
    allow-circular-references: true #解决循环依赖,暂时跳过
  application:
    name: gateway-server
  cloud:
    # https://cloud.tencent.com/developer/article/1650115?from=15425
    gateway:
      routes:
        # 消息服务
        - id: message-server
          # 匹配后路径 配合nacos服务名称
          uri: lb://message-server
          predicates:
            # 断言路劲,匹配成功后就走uri,多个用逗号分隔
            - Path=/api/msg/**
            #- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] 在什么时间段之前才匹配
            #- Cookie=username,zhangshuai #并且Cookie是username=zhangshuai才能访问
            #- Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式
            #- Host=**.tecloman.cn 主机名相同才能转发
            #- Method=GET  请求方法匹配
            #- Query=username, \d+ #要有参数名称并且是正整数才能路由
        # 计算服务
        - id: computer-server
          uri: lb://computer-server
          predicates:
            - Path=/energyStorageStation/**
        # web服务
        - id: hss-server
          #有多个hss-server服务,测试连不上生产的数据库,要超时报错
          #uri: lb://hss-server
          uri: http://localhost:1000
          predicates:
            - Path=/api/swagger/**,/api/hss/**,/api/ruralGrid/**
            #- Path=[/api/hss/**,/api/sys/**,/api/admin/**,/api/app/**,/api/openApi/**,/api/distributed/**,/api/ezviz/**,/api/ruralGrid/**,/api/swagger/**]
      # 全局的跨域处理
      globalcors:
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]': # 哪些访问地址做跨域处理
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8090"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000  # 这次跨域检测的有效期

使用 uri: http://localhost:1000 指定网址使用,打包到linux服务器,直接使用docker部署会出问题,docker容器中不能使用localhost,服务运行后应查询到IP地址后再修改gateway的配置再部署

docker inspect --format '{{ .NetworkSettings.IPAddress }}' <container-ID>
或
docker inspect <container id> 

代码配置

package gateway.server.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 *网关接口路由配置
 *@author chens
 *@date  2022-12-05更新
 */

public class GatewayConfig {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        // 通信服务的接口,因为包含在web服务里面,有点特殊
        return builder.routes()
                .route("hss-server", r -> r.path(
                        // path最好不要写死,
                        "/api/captcha.jpg/**",
                        "/api/ruralGrid/**",
                        "/api/test/**")
                        // 使用order来处理接口包含关系,web服务包含全部接口,
                        // 其它服务无法处理的情况,web服务最后执行
                        .and().order(0)
                        // 有多个重名的服务,本地连不上生产数据库,最好采用http方式
                        //.uri(url)
                        .uri("lb://cs-test-hss-server")
                )
                // 消息服务
                .route("message-server", r -> r.path(
                        "/api/msg/**")
                        .uri("lb://message-server"))
                // 运算服务
                .route("computer-server", r -> r.path(
                        "/energyStorageStation/**")
                        .uri("lb://computer-server"))
                // 通信服务
                .route("communications-server", r -> r.path(
                        "/api/hss/classify/**",
                        "/api/hss/type/**",
                        "/api/hss/strategy/**",
                        "/api/hss/protocol/**",
                        "/api/sys/script/**")
                        // 在web服务前面先执行,
                        .and().order(1)
                        .uri("lb://communications-server"))
                .build();
    }
    //跨域配置
    @Bean
    public CorsWebFilter corsWebFilter(){
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration configuration = new CorsConfiguration();
        // 配置跨域的信息
        configuration.addAllowedHeader("*");
        configuration.addAllowedMethod("*");
        // SpringBoot升级到2.4.0 之后需要使用该配置
        configuration.addAllowedOriginPattern("*");
        configuration.setAllowCredentials(true);
        source.registerCorsConfiguration("/**",configuration);
        return new CorsWebFilter(source);
    }
}

uri: lb://computer-server 采用服务名转发 需引入

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>

持久化

访问端点需要引入

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
management:
  endpoints:
    web:
      base-path: /root #根路劲 默认actuator
      exposure:
        include: "*" #暴露所有接口
    # server:
    #这和服务端口一样 那就没法走路由,过滤器不会生效
    # port: 8888

查看路由节点
localhost:8090/root/gateway/routes
在这里插入图片描述gateway提供的类GatewayControllerEndpoint 包含了crud的接口
gateway/routes 就是其中一个接口
现在我们自己写crud,因为gateway操作的全是内存上的数据,现在需要把数据存入数据库,项目启动从数据库读取配置
建表SQL

CREATE TABLE `gateway_route` (
	`id` INT NOT NULL AUTO_INCREMENT,
	`name` VARCHAR ( 255 ) CHARACTER 
	SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '路由名称',
	`route_id` VARCHAR ( 255 ) CHARACTER 
	SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '路由key',
	`uri` VARCHAR ( 255 ) CHARACTER 
	SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '转发URL',
	`predicates` json NOT NULL COMMENT '断言数据',
	`filters` json NOT NULL COMMENT '过滤数据',
	`order_num` INT DEFAULT NULL COMMENT '顺序',
	`state` TINYINT ( 1 ) NOT NULL DEFAULT '0' COMMENT '是否启用 0未启用 1启用',
	`remark` VARCHAR ( 255 ) CHARACTER 
	SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注',
	`create_time` datetime NOT NULL COMMENT '创建时间',
	`create_user_id` BIGINT NOT NULL COMMENT '创建人ID',
	`dtime` bit ( 1 ) NOT NULL DEFAULT b '0' COMMENT '逻辑删除标记',
PRIMARY KEY ( `id` ) USING BTREE 
) ENGINE = INNODB AUTO_INCREMENT = 17 DEFAULT CHARSET = utf8mb3 ROW_FORMAT = DYNAMIC COMMENT = '<dodo-server-app-manager>上架应用路由信息表';

持久框架 MP autoResultMap json字段自动映射

package gateway.server.hss.entity;

import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * 网关路由配置
 * @author chens
 */
@Data
@TableName(value = "gateway_route", autoResultMap = true)
public class GatewayRouterEntity extends Model<GatewayRouterEntity> implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId
    private Integer id;
    /**
     * 路由名称
     */
    private String name;
    /**
     * 路由key
     */
    private String routeId;
    /**
     * 转发URL
     */
    private String uri;
    /**
     * 断言数据
     */
    @TableField(typeHandler = JacksonTypeHandler.class)
    private JSONArray predicates;
    /**
     * 过滤数据
     */
    @TableField(typeHandler = JacksonTypeHandler.class)
    private JSONArray filters;
    /**
     * 备注
     */
    private String remark;
    /**
     * 执行顺序
     */
    private int orderNum = 0;
    /**
     * 状态 0未启用 1启用
     */
    private int state = 0;
    /**
     * 创建人ID
     */
    private Long createUserId;
    @TableField(exist = false)
    private String createUserName;
    private Date createTime;
    private int dtime = 0;
}

gateway只能使用ServerWebExchange 获取请求信息 ,不能使用HttpServletRequest
ServerWebExchange exchange

package gateway.server.hss.controller;

import gateway.server.util.PageUtils;
import gateway.server.util.R;
import gateway.server.hss.entity.GatewayRouterEntity;
import gateway.server.hss.service.GatewayRouteService;
import gateway.server.hss.service.impl.DynamicRouteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ServerWebExchange;

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

/**
 * 网关路由配置
 *
 * @author chens
 * @create 2022-12-7
 * @desc
 **/
//@Component
//@RestControllerEndpoint(id = "chen")
@RestController
@RequestMapping("/gateway/route")
public class GatawayRouteController {
    @Autowired
    private GatewayRouteService gatewayRouteService;
    private final DynamicRouteService dynamicRouteService;
    public GatawayRouteController(DynamicRouteService dynamicRouteService) {
        this.dynamicRouteService = dynamicRouteService;
    }

    @GetMapping("/list")
    public R list(ServerWebExchange exchange, Map<String, Object> params) {
        PageUtils page = gatewayRouteService.queryPage(params);
        return R.ok().put("page", page);
    }

    @PostMapping("/save")
    public R save(@RequestBody GatewayRouterEntity entity, ServerWebExchange exchange) {
        return this.dynamicRouteService.save(entity);
    }

    @PostMapping("/update")
    public R update(ServerWebExchange exchange, @RequestBody GatewayRouterEntity entity) {
        return this.dynamicRouteService.update(entity);
    }

    /**
     * 修改路由状态
     *
     * @param routeId 路由Id
     * @return
     */
    @PostMapping("/upState/{routeId}")
    public R upState(ServerWebExchange exchange,@PathVariable("routeId") String routeId) {
        return this.dynamicRouteService.upState(routeId);
    }
    /**
     * 删除路由
     *
     * @param routeId 路由Id
     * @return
     */
    @PostMapping("/delete/{routeId}")
    public R delete(ServerWebExchange exchange, @PathVariable("routeId") String routeId) {
        return this.dynamicRouteService.delete(routeId);
    }

    /**
     * 刷新路由
     *
     * @return
     */
    @GetMapping("/flush")
    public R flush(ServerWebExchange exchange) {
        return this.dynamicRouteService.flushRoute();
    }

}

主要CRUD类

package gateway.server.hss.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import gateway.server.util.R;
import gateway.server.hss.entity.GatewayRouterEntity;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
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.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @desc 动态路由配置
 **/
@Service
@Log4j2
public class DynamicRouteService implements ApplicationEventPublisherAware, ApplicationRunner {

    private final RouteDefinitionWriter routeDefinitionWriter;
    private GatewayRouteServiceImpl gatewayRouteServiceImpl;

    private ApplicationEventPublisher publisher;

    public DynamicRouteService(RouteDefinitionWriter routeDefinitionWriter, GatewayRouteServiceImpl gatewayRouteServiceImpl) {
        this.routeDefinitionWriter = routeDefinitionWriter;
        this.gatewayRouteServiceImpl = gatewayRouteServiceImpl;
    }


    /**
     * 增加路由
     *
     * @param gatewayRouterEntity
     * @return
     */
    public R save(GatewayRouterEntity gatewayRouterEntity) {
        GatewayRouterEntity one = getOne(gatewayRouterEntity.getRouteId());
        if (one != null) return R.error("路由ID已存在");
        RouteDefinition definition = convertGateway(gatewayRouterEntity);
        // 新增到内存中, 新增先暂不写入内存,更改状态再写入
        //routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        // 保存到数据库中
        saveData(gatewayRouterEntity.getName(), gatewayRouterEntity.getRemark(), definition);
        flushRouteConfig();
        return R.ok();
    }

    /**
     * 修改路由状态
     */
    public R upState(String routeId) {
        GatewayRouterEntity entity = getOne(routeId);
        // 状态取反,判断状态 决定删除内存中的路由还是新增到内存中
        if (entity == null) return R.error("路由不存在");
        RouteDefinition definition = convertGateway(entity);
        if (entity.getState() == 0) {
            entity.setState(1);
            // 启用 新增到内存中
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        } else if (entity.getState() == 1) {
            entity.setState(0);
            // 关闭 从内存中删除
            routeDefinitionWriter.delete(Mono.just(definition.getId())).subscribe();
        } else return R.error("路由状态异常");
        flushRouteConfig();
        // 状态取反 更新到数据库
        gatewayRouteServiceImpl.updateById(entity);
        return R.ok();
    }

    /**
     * 更新路由
     *
     * @param routeForm
     * @return
     */
    public R update(GatewayRouterEntity routeForm) {
        GatewayRouterEntity one = getOne(routeForm.getRouteId());
        if (one != null && !routeForm.getId().equals(one.getId()) ) {
            return R.error("路由ID已存在");
        }
        RouteDefinition definition = convertGateway(routeForm);
        try {
            routeDefinitionWriter.delete(Mono.just(definition.getId())).subscribe();
        } catch (Exception e) {
            return R.error("未知路由信息");
        }
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            saveData(routeForm.getName(), routeForm.getRemark(), definition);
            flushRouteConfig();
            return R.ok();
        } catch (Exception e) {
            return R.error("路由信息修改失败!");
        }
    }

    /**
     * 删除路由
     *
     * @param routeId 路由ID
     * @return
     */
    public R delete(String routeId) {
        this.routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
        gatewayRouteServiceImpl.remove(new QueryWrapper<GatewayRouterEntity>().lambda().eq(GatewayRouterEntity::getRouteId, routeId));
        flushRouteConfig();
        return R.ok();
    }

    /**
     * 刷新路由
     *
     * @return
     */
    private void flushRouteConfig() {
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }


    public R flushRoute() {
        flushRouteConfig();
        return R.ok();
    }

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


    @Override
    public void run(ApplicationArguments args) {
        log.info("----------从数据库加载额外路由信息---------");
        this.queryRoute();
    }

    // 从数据库查询配置
    private void queryRoute() {
        List<RouteDefinition> gatewayList = gatewayRouteServiceImpl.List();
        log.info("----------数据库路由数量:{}---------", gatewayList.size());
        // 数据库中的配置写入内存中
        gatewayList.forEach(x -> routeDefinitionWriter.save(Mono.just(x)).subscribe());
        flushRouteConfig();
    }


    /**
     * 实体转换成gateway实体
     *
     * @param entity
     * @return
     */
    private RouteDefinition convertGateway(GatewayRouterEntity entity) {
        RouteDefinition definition = new RouteDefinition();
        definition.setId(entity.getRouteId());
        definition.setOrder(entity.getOrderNum());
        //设置断言
        List<PredicateDefinition> predicateDefinitions = entity.getPredicates().stream()
                .distinct().map(x -> {
                    PredicateDefinition predicate = new PredicateDefinition();
                    Map object = (Map) x;
                    predicate.setArgs((Map) object.get("args"));
                    predicate.setName(object.get("name").toString());
                    return predicate;
                }).collect(Collectors.toList());
        definition.setPredicates(predicateDefinitions);

        // 设置过滤
        List<FilterDefinition> filterList = entity.getFilters().stream()
                .distinct().map(x -> {
                    FilterDefinition filter = new FilterDefinition();
                    Map object = (Map) x;
                    filter.setArgs((Map) object.get("args"));
                    filter.setName(object.get("name").toString());
                    return filter;
                }).collect(Collectors.toList());
        definition.setFilters(filterList);
        // 设置URI,判断是否进行负载均衡
        URI uri;
        if (entity.getUri().startsWith("http")) {
            uri = UriComponentsBuilder.fromHttpUrl(entity.getUri()).build().toUri();
        } else {
            uri = URI.create(entity.getUri());
        }
        definition.setUri(uri);
        return definition;
    }


    /**
     * 数据落库
     */
    public void saveData(String name, String remark, RouteDefinition definition) {
        String routeId = definition.getId();
        List<PredicateDefinition> predicates = definition.getPredicates();
        List<FilterDefinition> filters = definition.getFilters();
        int order = definition.getOrder();
        URI uri = definition.getUri();
        GatewayRouterEntity entity = new GatewayRouterEntity();
        entity.setName(name);
        entity.setRouteId(routeId);
        entity.setUri(uri.toString());
        entity.setPredicates(JSONArray.parseArray(JSON.toJSONString(predicates)));
        entity.setFilters(JSONArray.parseArray(JSON.toJSONString(filters)));
        entity.setRemark(remark);
        entity.setOrderNum(order);
        entity.setCreateUserId(1L);
        entity.setCreateTime(new Date());
        entity.setDtime(0);
        // 数据库不存在则保存,存在修改路由Id保存
        GatewayRouterEntity one = getOne(routeId);
        if (one == null) {
            gatewayRouteServiceImpl.save(entity);
        } else {
            entity.setId(one.getId());
            gatewayRouteServiceImpl.updateById(entity);
        }
    }

    public GatewayRouterEntity getOne(String routeId) {
        GatewayRouterEntity entity = gatewayRouteServiceImpl.getOne(new QueryWrapper<GatewayRouterEntity>().lambda()
                .eq(GatewayRouterEntity::getRouteId, routeId).last("limit 1"), false);
        return entity;
    }

}

查询类

package gateway.server.hss.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import gateway.server.util.PageUtils;
import gateway.server.util.Query;
import gateway.server.hss.entity.GatewayRouterEntity;
import gateway.server.hss.dao.RouteMapper;
import gateway.server.hss.service.GatewayRouteService;
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.Service;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @create 2022-12-7 14:13
 * @desc 路由信息持久化
 **/
@Service
public class GatewayRouteServiceImpl extends ServiceImpl<RouteMapper, GatewayRouterEntity> implements GatewayRouteService {
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage page = this.page(new Query<GatewayRouterEntity>().getPage(params),
                new QueryWrapper<GatewayRouterEntity>().eq("dtime", 0));
        return new PageUtils(page);
    }


    public List<RouteDefinition> List() {
        // 只查询启用状态的配置
        List<GatewayRouterEntity> list = list(new QueryWrapper<GatewayRouterEntity>().lambda()
                .eq(GatewayRouterEntity::getState, 1).eq(GatewayRouterEntity::getDtime, 0));
        return list.stream().map(x -> {
            RouteDefinition routeDefinition = new RouteDefinition();
            routeDefinition.setId(x.getRouteId());
            // 这里需要注意判空
            routeDefinition.setPredicates(JSONObject.parseArray(x.getPredicates().toJSONString(), PredicateDefinition.class));
            routeDefinition.setFilters(JSONObject.parseArray(x.getFilters().toJSONString(), FilterDefinition.class));
            try {
                routeDefinition.setUri(new URI(x.getUri()));
                routeDefinition.setOrder(x.getOrderNum());
                routeDefinition.setMetadata(new HashMap<>(2));
                return routeDefinition;
            } catch (URISyntaxException e) {
                return null;
            }
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }


}

新增和删除路由逻辑类

package gateway.server.hss.service.impl;

import lombok.extern.log4j.Log4j2;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author Administrator
 * @create 2022-12-07
 * @desc 自定义内存路由管理仓,开启日志打印
 **/
@Service
@Log4j2
public class DiyRouteDefinitionRepository implements RouteDefinitionRepository {

    public  final Map<String, RouteDefinition> routes = Collections.synchronizedMap(new LinkedHashMap<>());

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        Collection<RouteDefinition> values = routes.values();
        return Flux.fromIterable(values);
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap( r -> {
            log.info("新增路由信息:{}",r);
            routes.put(r.getId(), r);
            return Mono.empty();
        });
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id -> {
            log.info("删除路由信息,路由ID为:{}",id);
            if (routes.containsKey(id)) {
                routes.remove(id);
                return Mono.empty();
            }
            return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: "+routeId)));
        });
    }


}

数据结构


{
      "name":"通信路由",
      "routeId": "communications-server",
      "uri": "lb://communications-server",
      "order": 1,
      "predicates": [{
              "name": "Path",
              "args": {
                    "_genkey_0": "/api/hss/classify/**",
                    "_genkey_1": "/api/hss/type/**",
                    "_genkey_2": "/api/hss/strategy/**",
                    "_genkey_3": "/api/hss/protocol/**",
                    "_genkey_4": "/api/sys/script/**"
                         }
                          }],
      "remark":"测试自定义路由信息",
      "filters": [{
              "name": "StripPrefix",
              "args": {
                 "_genkey_0": "1"
                      }
                  }]
 }

predicates(断言) 和filters(过滤)新增配置说明


predicates, name和args固定key不可更改,首字母大写 ,args参数对象,_genkey_*代表其中一个参数,固定格式

name含义示例
Path指定路径匹配{“name”:“Path”,“args”:{“pattern”:“/aa/“,“pattern1”:”/bb/”}}
Cookie配置对Cookie中值的匹配,第一个为key,第二个为value{“name”:“Cookie”,“args”:{“_genkey_0”:“chocolate”,“_genkey_1”:“ch.p”}}
Header匹配Http请求中设置的内容{“name”:“Header”,“args”:{“_genkey_0”:“X-Request-Id”,“_genkey_1”:“\d+”}}
Host匹配Http请求Host,匹配所有host为**.tecloman.cn的请求{“name”:“Host”,“args”:{“_genkey_0”:“**.somehost.com”}}
Method匹配Http请求头{“name”:“Method”,“args”:{“_genkey_0”:“GET”}}
Query匹配Http请求中的查询参数,请求中携带{“name”:“Query”,“args”:{“_genkey_0”:“param1”,“_genkey_1”:“value”}}
RemoteAddr匹配请求中的RemoteAddr{“name”:“RemoteAddr”,“args”:{“_genkey_0”:“192.168.1.1/24”}}
After设置时间之后可以访问{“name”:“After”,“args”:{“_genkey_0”:“2017-01-20T17:42:47.789-07:00[America/Denver]”}}
Before设置时间之前可以访问{“name”:“Before”,“args”:{“_genkey_0”:“2017-01-20T17:42:47.789-07:00[America/Denver]”}}
Between设置时间段内可以访问{“name”:“Between”,“args”:{“_genkey_0”:“2017-01-20T17:42:47.789-07:00[America/Denver]”,“_genkey_1”:“2017-01-21T17:42:47.789-07:00[America/Denver]”}}
Weight两组以上路由可以配置权重路由{“name”:“Weight”,“args”:{“_genkey_0”:“service1”,“_genkey_1”:“80”}}

filters name 属性

name含义示例
RewritePath路径重写{“name”:“RewritePath”,“args”:{“_genkey_0”:“/foo/(?.*)”,“_genkey_1”:“/${segment}”}}
AddRequestHeader#### 修改请求头{“name”:“AddRequestHeader”,“args”:{“_genkey_0”:“X-Request-Foo”,“_genkey_1”:“Bar”}}
AddRequestParameter修改请求参数{“name”:“AddRequestParameter”,“args”:{“_genkey_0”:“foo”,“_genkey_1”:“bar”}}
AddResponseHeader修改响应参数{“name”:“AddResponseHeader”,“args”:{“_genkey_0”:“X-Request-Foo”,“_genkey_1”:“Bar”}}
PrefixPath路径前缀增强{“name”:“PrefixPath”,“args”:{“_genkey_0”:“/mypath”}}
StripPrefix路径前缀删除{“name”:“StripPrefix”,“args”:{“_genkey_0”:“2”}}
PreserveHostHeader请求携带保留原始Host{“name”:“PreserveHostHeader”,“args”:{}}
RedirectTo重定向{“name”:“RedirectTo”,“args”:{“_genkey_0”:“302”,“_genkey_1”:“http://acme.org”}}
Hystrix断路器{“name”:“Hystrix”,“args”:{“name”:“fallbackcmd”,“fallbackUri”:“forward:/incaseoffailureusethis”}}
RequestRateLimiter集成Redis原生支持请求限流{“name”:“RequestRateLimiter”,“args”:{“redis-rate-limiter.replenishRate”:“10”,“redis-rate-limiter.burstCapacity”:“20”}}
RemoveRequestHeader删除请求头属性{“name”:“RemoveRequestHeader”,“args”:{“_genkey_0”:“X-Request-Foo”}}
RemoveResponseHeader删除响应头属性{“name”:“RemoveResponseHeader”,“args”:{“_genkey_0”:“X-Request-Foo”}}
RewriteResponseHeader重写响应头{“name”:“RewriteResponseHeader”,“args”:{“_genkey_0”:“X-Response-Foo”,“_genkey_1”:“password=[^&]+”,“_genkey_2”:“password=***”}}
SetPath重设请求路径{“name”:“SetPath”,“args”:{“_genkey_0”:“/{segment}”}}
SetResponseHeader设置响应头{“name”:“SetResponseHeader”,“args”:{“_genkey_0”:“X-Response-Foo”,“_genkey_1”:“Bar”}}
SetStatus设置Http状态{“name”:“SetStatus”,“args”:{“_genkey_0”:“302”}}
RequestSize设置文件传输大小{“name”:“RequestSize”,“args”:{“_genkey_0”:“5000000”}}
Retry失败重试{“name”:“Retry”,“args”:{“_genkey_0”:3,“_genkey_1”:“BAD_GATEWAY”}}

相关接口


列表GETlocalhost:8090/gateway/route/list
刷新GETlocalhost:8090/gateway/route/flush
新增POSTlocalhost:8090/gateway/route/save
修改POSTlocalhost:8090/gateway/route/update
删除POSTlocalhost:8090/gateway/route/delete/{routeId}
修改状态POSTlocalhost:8090/gateway/route/upState/{routeId}
删除传入ID为路由的ID,修改状态传入为主键ID
网关端点localhost:8090/root/gateway/
查看正在运行的路由信息localhost:8090/root/gateway/routes
监控端点localhost:8090/root/

全局过滤器

经过网关路由转发 才能走到全局过滤器

package gateway.server.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import gateway.server.util.R;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;


/**
 * @author Administrator
 * @create 2022-12-07 17:19
 * @desc 全局过滤器
 */
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    @Value("${custom.version}")
    String version;
    @Value("${custom.enable}")
    Boolean enable;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        if (!enable) return chain.filter(exchange);
        // 获取前端传入的version
        ServerHttpRequest request = exchange.getRequest();
        String versionNum = request.getHeaders().getFirst("version");
        if (version.equals(versionNum)) {
            //当前放行,交由下个过滤器过滤
            return chain.filter(exchange);
        } else {
            String path = request.getURI().getPath();
            if ("/api/sys/login".equals(path)) {
                return chain.filter(exchange);
            }
            // 版本不一致,返版本回前端 强制刷新
            byte[] bytes = JSON.toJSONString(R.error(426, "error").put("version", version), SerializerFeature.WriteMapNullValue)
                    .getBytes(StandardCharsets.UTF_8);
            ServerHttpResponse response = exchange.getResponse();
            response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
            DataBuffer buffer = response.bufferFactory().wrap(bytes);
            //响应出去
            return response.writeWith(Flux.just(buffer));
        }
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

局部过滤器

处理某个服务转发 配置文件中需要指定

    gateway:
      routes:
        - id: web_route
          uri: lb://web-server
          predicates:
            - Path=/api/captcha.jpg/**,/api/ruralGrid/**
          filters:
            - AddResponseHeader=name,tecloman #添加响应头
            - Local #局部过滤器的前缀 LocalGatewayFilter  只取Local就行

注册局部过滤器,全局的不需要

package gateway.server.filter;

import gateway.server.filter.LocalGatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;

/**
 * @author Administrator
 */
@Component
public class LocalGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
    // 注册局部过滤器
    @Override
    public GatewayFilter apply(Object config) {
        return new LocalGatewayFilter();
    }
}
package gateway.server.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import gateway.server.util.R;
import gateway.server.util.SpringUtil;
import gateway.server.hss.dao.SysUserTokenDao;
import gateway.server.hss.entity.SysUserTokenEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

/**
 * @author chens
 * @create 2022-12-07 17:19
 * @desc 局部过滤器
 * 主要处理gateway本服务的接口和端点,其它服务绕过
 **/
@Component
public class LocalGatewayFilter implements GatewayFilter, Ordered {
    //private static SysUserTokenDao sysUserTokenDao = SpringUtil.getBean(SysUserTokenDao.class);
    @Autowired
    private SysUserTokenDao sysUserTokenDao;

    // 多个过滤器,决定顺序
    @Override
    public int getOrder() {
        return -1;
    }
    // 超级管理员才允许操作gateway相关接口 ,需要路由转发才生效
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        HttpHeaders headers = exchange.getRequest().getHeaders();
        String token = headers.getFirst("token");
        SysUserTokenEntity entity = sysUserTokenDao.selectById(1);
        String msg = "无权限查看此接口";
        if (entity != null) {
            if (entity.getToken().equals(token)) {
                if (entity.getExpireTime() > System.currentTimeMillis() / 1000) {
                    return chain.filter(exchange);
                } else {
                    msg = "token失效,请重新登录";
                }
            }
        }
        byte[] bytes = JSON.toJSONString(R.error(401, msg), SerializerFeature.WriteMapNullValue)
                .getBytes(StandardCharsets.UTF_8);
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        DataBuffer buffer = response.bufferFactory().wrap(bytes);
        //响应出去
        return response.writeWith(Flux.just(buffer));

    }
}

全局异常处理

不能像spring boot那样类上打个@RestControllerAdvice注解使用

package gateway.server.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import gateway.server.util.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;


/**
 * @author Administrator
 * gateway全局异常处理
 */
@Slf4j
@Order(-1)
@Component
public class CustomWebExceptionHandler implements WebExceptionHandler {

    private static final Set<String> DISCONNECTED_CLIENT_EXCEPTIONS;

    //排除部份系统级的异常
    static {
        Set<String> exceptions = new HashSet<>();
        exceptions.add("AbortedException");
        exceptions.add("ClientAbortException");
        exceptions.add("EOFException");
        exceptions.add("EofException");
        DISCONNECTED_CLIENT_EXCEPTIONS = Collections.unmodifiableSet(exceptions);
    }

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        if (exchange.getResponse().isCommitted() || isDisconnectedClientError(ex)) {
            return Mono.error(ex);
        }
        ServerHttpRequest request = exchange.getRequest();
        String rawQuery = request.getURI().getRawQuery();
        String query = StringUtils.hasText(rawQuery) ? "?" + rawQuery : "";
        String path = request.getPath() + query;
        String message;
        HttpStatus status = determineStatus(ex);
        if (status == null) {
            status = HttpStatus.INTERNAL_SERVER_ERROR;
        }
        // 通过状态码自定义异常信息
        if (status.value() >= 400 && status.value() < 500) {
            message = "路由服务不可达或禁止访问!";
        } else {
            message = "路由服务异常!" + ex.getMessage();
        }
        message += " path:" + path;
        //工具类输出json字符串
        byte[] bytes = JSON.toJSONString(R.error(status.value(), message), SerializerFeature.WriteMapNullValue)
                .getBytes(StandardCharsets.UTF_8);
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        DataBuffer buffer = response.bufferFactory().wrap(bytes);
        //响应出去
        return response.writeWith(Flux.just(buffer));
    }

    @Nullable
    protected HttpStatus determineStatus(Throwable ex) {
        if (ex instanceof ResponseStatusException) {
            return ((ResponseStatusException) ex).getStatus();
        }
        return null;
    }

    private boolean isDisconnectedClientError(Throwable ex) {
        return DISCONNECTED_CLIENT_EXCEPTIONS.contains(ex.getClass().getSimpleName())
                || isDisconnectedClientErrorMessage(NestedExceptionUtils.getMostSpecificCause(ex).getMessage());
    }

    private boolean isDisconnectedClientErrorMessage(String message) {
        message = (message != null) ? message.toLowerCase() : "";
        return (message.contains("broken pipe") || message.contains("connection reset by peer"));
    }
}

如果是在gateway本服务写的接口还需要添加跨域配置

package gateway.server.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;


import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

/**
 * 本地接口的跨域配置
 */
@Configuration
public class CorsConfig{
    @Bean
    public WebFilter corsFilter() {
        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();
            if (CorsUtils.isCorsRequest(request)) {
                HttpHeaders requestHeaders = request.getHeaders();
                ServerHttpResponse response = ctx.getResponse();
                HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
                HttpHeaders headers = response.getHeaders();
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
                headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,
                        requestHeaders.getAccessControlRequestHeaders());
                if (requestMethod != null) {
                    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
                }
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
                headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
                if (request.getMethod() == HttpMethod.OPTIONS) {
                    response.setStatusCode(HttpStatus.OK);
                    return Mono.empty();
                }
            }
            return chain.filter(ctx);
        };
    }
}

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值