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"));
}
}
如果是在gateway本服务写的接口还需要添加跨域配置
package gateway.server.config;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.http.httpheaders;
import org.springframework.http.httpmethod;
import org.springframework.http.httpstatus;
import org.springframework.http.server.reactive.serverhttprequest;
import org.springframework.http.server.reactive.serverhttpresponse;
import org.springframework.web.cors.reactive.corsutils;
import org.springframework.web.server.serverwebexchange;
import org.springframework.web.server.webfilter;
import org.springframework.web.server.webfilterchain;
import reactor.core.publisher.mono;
/**
* 本地接口的跨域配置
*/
@configuration
public class corsconfig{
@bean
public webfilter corsfilter() {
return (serverwebexchange ctx, webfilterchain chain) -> {
serverhttprequest request = ctx.getrequest();
if (corsutils.iscorsrequest(request)) {
httpheaders requestheaders = request.getheaders();
serverhttpresponse response = ctx.getresponse();
httpmethod requestmethod = requestheaders.getaccesscontrolrequestmethod();
httpheaders headers = response.getheaders();
headers.add(httpheaders.access_control_allow_origin, requestheaders.getorigin());
headers.addall(httpheaders.access_control_allow_headers,
requestheaders.getaccesscontrolrequestheaders());
if (requestmethod != null) {
headers.add(httpheaders.access_control_allow_methods, requestmethod.name());
}
headers.add(httpheaders.access_control_allow_credentials, "true");
headers.add(httpheaders.access_control_expose_headers, "*");
if (request.getmethod() == httpmethod.options) {
response.setstatuscode(httpstatus.ok);
return mono.empty();
}
}
return chain.filter(ctx);
};
}
}
发表评论