文章目录
- 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”}} |
相关接口
列表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;
@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"));
}
}