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

news2024/11/24 17:16:54

文章目录

    • yml配置
    • 代码配置
    • 持久化
    • 数据结构
    • predicates(断言) 和filters(过滤)新增配置说明
    • 相关接口
    • 全局过滤器
    • 局部过滤器
    • 全局异常处理

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"));
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/116565.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Unity】Delegate, Event, UnityEvent, Action, UnityAction, Func 傻傻分不清

【Unity】Delegate, Event, UnityEvent, Action, UnityAction, Func 傻傻分不清Delegate 委托&#xff0c;函数指针一个简单的例子&#xff1a;一对一依赖一个简单的例子&#xff1a;一对多依赖所以话说……委托有啥用呢&#xff1f;事件 Event&#xff0c;特殊的委托UnityEven…

Failed to read artifact descriptor for XXX.jar错误分析

今天遇到极其恶心的坑&#xff0c;自己定义编写的工具类&#xff0c;之前使用一直没有问题&#xff0c;正常引入pom文件中&#xff0c;也能正常的使用工具类中自建类。今天就是无法导入包&#xff0c;报错信息如下。 Failed to read artifact descriptor for com.yuxuntoo.www…

JavaScript篇.day03-循环结构(while, for)

目录 1.循环结构 2.例题 (1)奇数乘积(while/do-while) (2)水仙花数(while) (3)最大公约数(for) (4)最小公倍数(for) (5)打印星星(for) (6)乘法表(for) 1.循环结构 循环结构在程序中可执行多次循环结构有while循环, do-while循环, for循环 名称执行顺序循环执行次数应用…

wireshark练习抓取网络数据包用C编程完成modbus协议,从云端服务器读取温湿度数据。

文章目录前言一、疯狂聊天室1、配置网络2、创建房间3、互发消息二、wireshark抓取网络数据包1.找到对应的包2、分析抓取包中的信息三、Modbus协议概述1、Modbus主/从协议原理2、通用Modbus帧结构---协议数据单元(PDU)3、两种Modbus串行传输模式4、ModbusTCP通信结构四、C编程完…

零基础自学网络安全,如何3个月快速入门?

说到自学网络安全这一块&#xff0c;我来给大家分享一下我的经验。 一、了解相关网站 在入这行之前&#xff0c;我是先泡了一段时间网络安全相关的论坛&#xff0c;了解行业的信息&#xff0c;也可以确认自己是否真的想做这一行&#xff0c;毕竟这一行看起来很炫酷&#xff0…

Python编程 内置文件中常用方法

作者简介&#xff1a;一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.函数 &#xff08;1&#xff09;range() &#xff08;2&#xff09;zi…

做自动化测试3年,薪资为何会被应届生倒挂?

各位职场人都听说过薪资倒挂这词儿吧&#xff0c;这个情况在行业内早就不是什么稀罕事了&#xff0c;我有一个认识的&#xff0c;作为公司3年工龄的软件测试老员工&#xff0c;技术过关&#xff0c;能够独立工作&#xff0c;并且思考优化方向&#xff0c;我不吹牛的说&#xff…

【STL学习之路】vector的使用

文章目录vector介绍vector使用一、构造函数二、迭代器三、vector增删查改关于STL中参数的分析&#xff08;以push_back为例&#xff09;sort算法和仿函数使用vector介绍 vector就是顺序表的封装&#xff0c;是一个模板类&#xff0c;如上图所示。为方便vector里可以存任何数据类…

ChatGPT 免账号使用

一.ChatGPT 简介 ChatGPT是人工智能研究实验室OpenAI新推出的一种人工智能技术驱动的自然语言处理工具&#xff0c;使用了Transformer神经网络架构&#xff0c;也是GPT-3.5架构&#xff0c;这是一种用于处理序列数据的模型&#xff0c;拥有语言理解和文本生成能力&#xff0c;尤…

搭建资金运营体系提高企业的运营能力

资金运营体系是以项目资金流管理为核心&#xff0c;在项目预核算体系基础上的深度化&#xff0c;是丰富和完善全面预算管理的重要内容。资金运营体系建设嵌入到业务流程和项目实施过程&#xff0c;将资金使用成本贯穿于项目实施各个环节。 一、资金管控总体思路 1、资金运营的…

数据可视化:春节临近,拥堵模式开启,你买好回家的车票了吗

哈喽&#xff0c;大家好&#xff0c;春节将近&#xff0c;想必大家也开始准备回家过年的事情了&#xff0c;春运即将开始&#xff0c;祝愿大家都能买到回家的车票。 想必大家对春运都不陌生&#xff0c;随着春节临近&#xff0c;全国各地也都先后开启了拥堵模式&#xff0c;下…

unity打android studio项目包运行时报错

Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0. 打开Cmd 进入打包的项目&#xff0c;如下 输入gradlew --warning-mode all 说的是在Build.gradle(project)的RepositoryHandler.jcenter()方法已弃用 改成mavenCentral()即可 …

【OpenCV-Python】教程:8-3 高动态范围 HDR

OpenCV Python HDR 【目标】 学习如何从曝光序列生成和显示HDR图像。使用曝光融合来合并曝光序列。 【理论】 高动态范围成像(HDRI或HDR)是一种用于成像和摄影的技术&#xff0c;用于再现比标准数字成像或摄影技术更大的动态范围的光度。虽然人眼可以适应广泛的光照条件&am…

软考中级软件设计师和系统集成项目管理工程师哪个更好考?

软件设计师&#xff1a; 计算机相关基础知识&#xff0c;常用数据结构和常用算法&#xff0c;C程序设计语言&#xff0c;以及C、Java中的一种程序设计语言&#xff0c;软件工程、软件过程改进和软件开发项目管理的基础知识&#xff0c;软件设计的方法和技术。 系统集成项目管…

【设计模式】软件开发原则

开闭原则&#xff08;OCP&#xff09; 定义&#xff1a;一个软件实体应当对扩展开放&#xff0c;对修改关闭。也就是说在设计一个模块的时候&#xff0c;应当使这个模块可以在不被修改的前提下被扩展&#xff0c;即使现在不修改源代码的情况下改变这个模块的行为。 意思&#x…

(包含源代码)2022年全国大学生数学建模竞赛E题目-小批量物料生产安排详解+思路+Python代码时序预测模型-补完

目录 前言 赛题分析 1.问题一 问题分析 物料频率 代码详细操作&#xff1a; 出现频次 需求总数 趋势标量 方法 平均每天需求额度 整合代码 熵权法 详细介绍&#xff1a; 二、使用步骤 2.计算指标信息熵 3.熵权法相关代码 得到权重&#xff1a; 只希望各位以…

决策树-sklearn

决策树 1.概述 1.1 决策树是如何工作 决策树能够从一系列有特征和标签的数据中总结出决策规则&#xff0c;并且使用树状图的结构来表现&#xff0c;从而达到解决回归和分类问题。通俗的说&#xff0c;就是我们只需要问一系列问题就可以对数据进行分类。 核心要解决的问题&am…

20221226编译Toybrick的TB-RK3588X开发板的Android12系统3

20221226编译Toybrick的TB-RK3588X开发板的Android12系统3 2022/12/26 18:27 编译指令&#xff1a; 1、cat android12-rk3588.tar.gz.* >android12-rk3588.tar.gz 2、tar -zxvf android12-rk3588.tar.gz,解压缩后生成android12-rk3588-new目录 3、android12-rk3588-new 4、…

CSS篇.day04-单位,流式布局,弹性布局(flex),网格布局,渐变,变形与过渡

目录 1. 单位 2.流式布局 3.弹性布局(flex) 4.网格布局 5.渐变 6.变形与过渡 1. 单位 px: 绝对单位 像素em: 相对单位 基准点为父节点字体大小 若自身定义了font-size按自身来计算(一般浏览器默认16px)rem: 相对单位 相对根节点html的字体大小, css3新增属性, 根元…

【Python百日进阶-数据分析】Day139 - plotly甘特图:plotly.figure_factory.create_gantt()

文章目录一、语法二、参数三、返回值四、实例4.1 普通ff甘特图4.2 将任务组合在一起4.3 按数值变量着色4.4 create_gantt4.5具有数字条目的按列索引4.6 具有字符串条目的按列索引4.7 使用颜色字典4.8一、语法 已弃用&#xff0c;请用plotly.express.timeline()代替。 plotly.…