文章目录
- 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-servercloud:# https://cloud.tencent.com/developer/article/1650115?from=15425gateway:routes:# 消息服务- id: message-server# 匹配后路径 配合nacos服务名称uri: lb://message-serverpredicates:# 断言路劲,匹配成功后就走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-serveruri: lb://computer-serverpredicates:- Path=/energyStorageStation/**# web服务- id: hss-server#有多个hss-server服务,测试连不上生产的数据库,要超时报错#uri: lb://hss-serveruri: http://localhost:1000predicates:- 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 # 是否允许携带cookiemaxAge: 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 {@Beanpublic 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();}//跨域配置@Beanpublic 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 #根路劲 默认actuatorexposure: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;@TableIdprivate 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 {@Autowiredprivate 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();}@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher publisher) {this.publisher = publisher;}@Overridepublic 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 {@Overridepublic 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<>());@Overridepublic Flux<RouteDefinition> getRouteDefinitions() {Collection<RouteDefinition> values = routes.values();return Flux.fromIterable(values);}@Overridepublic Mono<Void> save(Mono<RouteDefinition> route) {return route.flatMap( r -> {log.info("新增路由信息:{}",r);routes.put(r.getId(), r);return Mono.empty();});}@Overridepublic 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”}} |
相关接口
列表GET | localhost:8090/gateway/route/list |
---|---|
刷新GET | localhost:8090/gateway/route/flush |
新增POST | localhost:8090/gateway/route/save |
修改POST | localhost:8090/gateway/route/update |
删除POST | localhost:8090/gateway/route/delete/{routeId} |
修改状态POST | localhost: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;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {if (!enable) return chain.filter(exchange);// 获取前端传入的versionServerHttpRequest 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));}}@Overridepublic int getOrder() {return -1;}
}
局部过滤器
处理某个服务转发 配置文件中需要指定
gateway:routes:- id: web_routeuri: lb://web-serverpredicates:- 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> {// 注册局部过滤器@Overridepublic 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);@Autowiredprivate SysUserTokenDao sysUserTokenDao;// 多个过滤器,决定顺序@Overridepublic int getOrder() {return -1;}// 超级管理员才允许操作gateway相关接口 ,需要路由转发才生效@Overridepublic 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);}@Overridepublic 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));}@Nullableprotected 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{@Beanpublic 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);};}
}