前言

关于引入gateway的好处我网上找了下:

  • 性能:API高可用,负载均衡,容错机制。
  • 安全:权限身份认证、脱敏,流量清洗,后端签名(保证全链路可信调用),黑名单(非法调用的限制)。
  • 日志:日志记录(spainid,traceid)一旦涉及分布式,全链路跟踪必不可少。
  • 缓存:数据缓存。监控:记录请求响应数据,api耗时分析,性能监控。
  • 限流:流量控制,错峰流控,可以定义多种限流规则。
  • 灰度:线上灰度部署,可以减小风险。
  • 路由:动态路由规则。

配置

依赖

compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
compile('org.springframework.cloud:spring-cloud-starter-gateway')
compile("org.springframework.cloud:spring-cloud-starter-openfeign")
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"

application加注解

@EnableFeignClients
@EnableDiscoveryClient

yml

ribbon:
ConnectTimeout: 60000
ReadTimeout: 60000
eureka:
enabled: true spring:
profiles:
active: dev
application:
name: web-gateway
cloud:
gateway:
discovery:
locator:
enabled: false
lower-case-service-id: true
routes:
- id: pc-api
uri: lb://pc-api
order: -1
predicates:
- Path=/api/pc/**
filters:
- StripPrefix=2
- SwaggerHeaderFilter
- id: admin-api
uri: lb://admin-api
order: -1
predicates:
- Path=/api/admin/**
filters:
- StripPrefix=2
- SwaggerHeaderFilter #swagger过滤器
- AdminAuthFilter=true #管理后台自定义过虑器
- id: open-api
uri: lb://open-api
order: -1
predicates:
- Path=/api/open/**
filters:
- StripPrefix=2
- SwaggerHeaderFilter
#白名单(不鉴权)
setting:
whiteUrls:
- "/api/admin/auth/login"
- "/api/admin/v2/api-docs"
- "/api/pc/v2/api-docs"
- "/api/open/v2/api-docs" --- spring:
profiles: dev
redis:
host: 10.10.10.35
port: 6379
password: root eureka:
instance:
prefer-ip-address: true
#Eureka服务端在收到最后一次心跳之后等待的时间上限,单位为秒,超过则剔除
lease-expiration-duration-in-seconds: 30
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒
lease-renewal-interval-in-seconds: 15
client:
serviceUrl:
defaultZone: http://10.10.10.35:8761/eureka/ --- spring:
profiles: uat
redis:
host: 172.17.0.12
port: 6379
password: root eureka:
instance:
prefer-ip-address: true
client:
serviceUrl:
defaultZone: http://172.17.0.12:8761/eureka/

全局过滤

@Slf4j
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered { private static final String START_TIME = "startTime"; @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
ServerHttpRequest serverHttpRequest = exchange.getRequest();
String ip = IpUtil.getIp(serverHttpRequest);
String method = serverHttpRequest.getMethod().name();
String requestURI = serverHttpRequest.getURI().getPath();
String token = serverHttpRequest.getHeaders().getFirst("token"); return chain.filter(exchange).then( Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute(START_TIME);
if (startTime != null) {
Long executeTime = (System.currentTimeMillis() - startTime);
log.info(String.format("%s >>> %s >>> %s >>> %s >>> %s ms", requestURI, method, ip, token, executeTime));
}
})); } @Override
public int getOrder() {
return -2;
} }

登录过滤

@Slf4j
@Component
public class AdminAuthFilter extends AbstractGatewayFilterFactory implements Ordered { @Autowired
private GatewaySetting gatewaySetting; @Autowired
private RedisUtil redisUtil; @Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String requestURI = "/api/admin"+request.getURI().getPath();
if(gatewaySetting.getWhiteUrls().contains(requestURI)){
return chain.filter(exchange);
}
boolean isCookieToken = false;
String token = request.getHeaders().getFirst("token");
if(StringUtils.isEmpty(token)){
MultiValueMap<String, HttpCookie> cookieValueMap = request.getCookies();
log.debug("cookieValueMap===============>"+ JSON.toJSONString(cookieValueMap));
if(cookieValueMap.containsKey(GlobalConstant.ADMIN_LOGIN_TOKEN_COOKIE_NAME)){
HttpCookie cookie = cookieValueMap.getFirst(GlobalConstant.ADMIN_LOGIN_TOKEN_COOKIE_NAME);
token = cookie.getValue();
isCookieToken = true;
}
}
if(StringUtils.isEmpty(token)){
return FilterUtil.failResponse(exchange, Code.UNAUTHORIZED,"非法访问");
}
if(!redisUtil.hasKey(RedisKeyConstant.adminApiAuthLoginToken+token)){
return FilterUtil.failResponse(exchange,Code.EXPIRE_LOGIN,"登录过期");
}
if(isCookieToken){
ServerHttpRequest host = exchange.getRequest().mutate().header("token", token).build();
ServerWebExchange build = exchange.mutate().request(host).build();
return chain.filter(build);
}
return chain.filter(exchange);
};
} @Override
public int getOrder() {
return 1;
}
}

白名单配置

@Getter
@Setter
@ConfigurationProperties("setting")
@Component
public class GatewaySetting { private List<String> whiteUrls; }

工具类

public class FilterUtil {

    public static Mono<Void> failResponse(ServerWebExchange exchange, Code code, String msg){
ServerHttpResponse response = exchange.getResponse();
Result resp = Result.of(code,msg);
byte[] bits = JSON.toJSONString(resp).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//指定编码,否则在浏览器中会中文乱码
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
} }

public class IpUtil { private static final Log log = LogFactory.getLog(IpUtil.class); public static String getIp(ServerHttpRequest request) {
String ip=null; List<String> headers = request.getHeaders().get("X-Real-IP");
if(headers!=null&&headers.size()>=1)
ip = headers.get(0); if (!StringUtils.isEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
log.debug(">>>>>>>>>>>>>>>>>>>>>X-Real-IP获取到ip:"+ip);
return ip;
}
headers = request.getHeaders().get("X-Forwarded-For");
if (!StringUtils.isEmpty(headers) && headers.size()>=1) {
// 多次反向代理后会有多个IP值,第一个为真实IP。
ip = headers.get(0);
int index = ip.indexOf(',');
if (index != -1) {
log.debug(">>>>>>>>>>>>>>>>>>>>>X-Forwarded-For获取到ip:"+ip);
return ip.substring(0, index);
} else {
return ip;
}
} else {
log.debug(">>>>>>>>>>>>>>>>>>>>>RemoteAddress获取到ip:"+ip);
return request.getRemoteAddress().getAddress().getHostAddress();
}
}
}

集成swagger

@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources; @Autowired
public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
} @GetMapping("/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
} @GetMapping("/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
} @GetMapping("")
public Mono<ResponseEntity> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory { private static final String HEADER_NAME = "X-Forwarded-Prefix"; @Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (!StringUtils.endsWithIgnoreCase(path, SwaggerProvider.API_URI)) {
return chain.filter(exchange);
}
//String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URI));
String referName = "后台API";
String referUrl = exchange.getRequest().getHeaders().get("Referer").get(0);
if (referUrl.indexOf("=") > -1) {
referName = referUrl.split("=")[1];
}
String basePath = "";
try {
basePath = SwaggerProvider.moduleMap.get(URLDecoder.decode(referName, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
};
}
}
@Component
@Primary
//@Profile({"dev","test"})
public class SwaggerProvider implements SwaggerResourcesProvider { public static final String API_URI = "/v2/api-docs"; public static Map<String, String> moduleMap = new HashMap<>(); static {
moduleMap.put("后台API", "/api/admin");
moduleMap.put("PC端API", "/api/pc");
moduleMap.put("开放平台", "/api/open");
} @Override
public List<SwaggerResource> get() {
List resources = new ArrayList<>();
moduleMap.forEach((k, v) -> {
resources.add(swaggerResource(k, v));
});
return resources;
} private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location + API_URI);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}

Springcloud之gateway配置及swagger集成的更多相关文章

  1. 【SpringCloud】Gateway 配置全局过滤器获取请求参数和响应值

    [SpringCloud]Gateway 配置全局过滤器获取请求参数和响应值 实现Ordered接口getOrder()方法,数值越小越靠前执行,记得这一点就OK了. 获取请求参数RequestBod ...

  2. SpringBoot系列十一:SpringBoot整合Restful架构(使用 RestTemplate 模版实现 Rest 服务调用、Swagger 集成、动态修改日志级别)

    声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.概念:SpringBoot整合Restful架构 2.背景 Spring 与 Restful 整合才是微架构的核心,虽然在整 ...

  3. springcloud(七):配置中心svn示例和refresh

    上一篇springcloud(六):配置中心git示例留了一个小问题,当重新修改配置文件提交后,客户端获取的仍然是修改前的信息,这个问题我们先放下,待会再讲.国内很多公司都使用的svn来做代码的版本控 ...

  4. Springcloud 中 SpringBoot 配置全集 (收藏版)

    Springcloud 中 SpringBoot 配置全集 (收藏版) 疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 前言 疯狂创客圈(笔者尼恩创建的高并发研习社群 ...

  5. Spring Gateway配置使用(一)

    参考文档:Spring Gateway官方文档 , 玹霖的博客 1.Spring Gateway简介 Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring ...

  6. Swagger详解(SpringBoot+Swagger集成)(转)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/ai_miracle/article/de ...

  7. 【转】Swagger详解(SpringBoot+Swagger集成)

    Swagger-API文档接口引擎Swagger是什么 Swagger是一个规范和完整的框架,用于生成.描述.调用和可视化 RESTful 风格的 Web 服务.总体目标是使客户端和文件系统作为服务器 ...

  8. springcloud(九):配置中心和消息总线(配置中心终结版)

    我们在springcloud(七):配置中心svn示例和refresh中讲到,如果需要客户端获取到最新的配置信息需要执行refresh,我们可以利用webhook的机制每次提交代码发送请求来刷新客户端 ...

  9. [转]springcloud(九):配置中心和消息总线(配置中心终结版)

    https://www.cnblogs.com/ityouknow/p/6931958.html springcloud(九):配置中心和消息总线(配置中心终结版) 我们在springcloud(七) ...

随机推荐

  1. HTML/css清除浮动的几种方式

    浮动在HTML/CSS布局中十分常见,虽然浮动布局可以实现很多有用的排版效果,但是浮动的元素脱离了文档流,可能造成包含块高度塌陷.下方行框上移等行为.因此在使用浮动实现想要的效果后,通常还需要清除浮动 ...

  2. CSS之calc()

    calc() 函数支持任意CSS长度单位的混合计算,遵循标准数学运算优先级规则,可以动态计算长度值.注意,calc()函数内部的运算符两侧各加一个空白符,否则会产生解析错误. calc()使用的难点在 ...

  3. MySql Binlog 说明 & Canal 集成MySql的更新异常说明 & MySql Binlog 常用命令汇总

    文章来源于本人的印象笔记,如出现格式问题可访问该链接查看原文 原创声明:作者:Arnold.zhao 博客园地址:https://www.cnblogs.com/zh94 目录 背景介绍 开启MySq ...

  4. Ceph OSD从filestore 转换到 bluestore的方法

    前言 前段时间看到豪迈的公众号上提到了这个离线转换工具,最近看到群里有人问,找了下没什么相关文档,就自己写了一个,供参考 实践步骤 获取代码并安装 git clone https://github.c ...

  5. 设置Centos7中vim与vi编辑器显示行号

    设置Centos7中vim与vi编辑器的行号 步骤一: 输入命令设置: 1.vim ~/.vimrc 或者:(vi ~/.vimrc) 步骤二: 输入命令保存: 1.在其中输入 "set n ...

  6. Mysql预处理语句prepare、execute、deallocate

    前言 做CTF题的时候遇到的所以参考资料学习一波.... MySQL的SQL预处理(Prepared) 一.SQL 语句的执行处理 1.即时 SQL 一条 SQL 在 DB 接收到最终执行完毕返回,大 ...

  7. centos虚拟机配置网络

    首先想用xshell链接虚拟机,发现没有联网, 首先打开Windows下的服务 将虚拟机的网络设置为net模式 centos的网卡默认是关闭的,需要手动打开,首先进入这个目录 用vim打开ifcfg- ...

  8. 一文带你读懂!华为云在ACMUG技术沙龙上都透露了些啥?

    摘要:近日,华为云数据库业务总裁苏光牛在ACMUG中国MySQL用户组主办的 "华为云专场" 技术沙龙中分享了华为云数据库重磅新品GaussDB的核心能力与竞争优势.那么, Gau ...

  9. 插件Spire.PDF帮你高效搞定PDF打印

    Spire.PDF介绍 Spire.PDF是一个专业的PDF组件,能够独立地创建.编写.编辑.操作和阅读PDF文件,支持 .NET.Java.WPF和Silverlight.Spire.PDF的PDF ...

  10. SQL server分页的四种方法(算很全面了)

      这篇博客讲的是SQL server的分页方法,用的SQL server 2012版本.下面都用pageIndex表示页数,pageSize表示一页包含的记录.并且下面涉及到具体例子的,设定查询第2 ...