大家周末愉快啊!!!
今天趁加班忙完了,写一篇关于 SpringCloud 网关(geteway)的文章。
简单说下需求:
1、转发请求
2、响应头可修改
3、实现跨域
4、兼容其他项目
最近做的网关项目,分享出一个例子给大家,已上传至 GitHub,可自行下载。亲,记得 fork 一下哦
下载链接:https://github.com/313989006/springcloud-gateway
虽然说这个项目没多大的复杂性,但是对于开始使用SpringCloud的程序猿们来说,还是很头大的。why?
里面很多的不兼容性,就让你会头大的不得了,更别说走一步错一步了。所以,盆友们,需要多点耐心哟。
这里有些地方不方便贴出来,如果有问题的话可以私信我。
众所周知,SpringCloud只是将Springboot更好的利用了一番,所以SpringCloud项目都是Springboot架构的。
SpringCloud 网关,这里我们使用Eureka做,为什么用Eureka呢?
原因如下:
a、遵循AP原则
b、Eureka Server 也可以运行多个实例来构建集群,解决单点问题
c、去中心化
d、自我保护机制
这里主要分为2个部分来讲,另外一个部分是一个demo用例。
一、Eureka 客户端的搭建
新建Springboot项目,取名demo-eureka-server,新建applicaiton.yml文件,内容如下
server:
#端口号
port: 8761
#eureka:
eureka:
instance:
hostname: eureka地址
client:
# 是否注册到eurekaserver
registerWithEureka: false
# 是否拉取信息
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
spring:
application:
name: eurka-server
新建Springboot项目启动类:EurekaServerApplication
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run( EurekaServerApplication.class, args );
}
}
修改pom.xml文件
4.0.0
com.mxk.demo
demo-eureka-server
0.0.1-SNAPSHOT
jar
demo-eureka-server
Demoproject for Spring Boot
org.springframework.boot
spring-boot-starter-parent
2.1.3.RELEASE
UTF-8
UTF-8
1.8
Finchley.RELEASE
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-eureka-server
1.3.1.RELEASE
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
2.0.1.RELEASE
第一个项目搭建完成,Eureka 客户端就是如此简单。
二、搭建 demo-gateway项目,这里很重要,负责转发和提供其他需求
新建application.yml文件,并编辑如下:
注:
1、这里要说一下,大家可能会遇到各种兼容性的问题,SpringCloud和web等的兼容性,但是也不用着急,可以慢慢解决。
在这里是没必要去解决的了,我已经解决了。
2、routes - id 自行取名,尽量每个id不要重复
routes -uri 对应的是每个服务的服务地址和端口号
routes -predicates 对应的是端口号后面的根路径名字,加上/**,可以请求到根路径下的所有api。
filters - StripPrefix=0 设置为0,是不过滤根路径,如果想过滤掉的话,直接设置为1即可。
filters: - Login=true 这里指的是,当前网关服务对应的过滤器是 LoginGatewayFilterFactory
server:
#端口号
port: 8081
spring:
application:
name: service-gateway
main:
allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
cloud:
gateway:
discovery:
locator:
# 启用eureka discovery locator:true
# 禁用eureka discovery locator:false,则不能通过路径http://localhost:8081/service-hi/hi?name=mxk访问
enabled: false
lowerCaseServiceId: true
routes:
- id: service-hi
uri: lb://SERVICE-HI
predicates:
- Path=/demo/**
filters:
- StripPrefix=1
- Login=true
token:
timeout: 600
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
logging:
level:
org.springframework.cloud.gateway: debug
新建ServiceGatewayApplication 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
@EnableEurekaClient
public class ServiceGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceGatewayApplication.class, args);
}
@Bean
public LoginGatewayFilterFactory loginGatewayFilter() {
return new LoginGatewayFilterFactory();
}
}
修改pom.xml文件,如下
4.0.0
com.mxk.demo
demo-gateway
0.0.1-SNAPSHOT
jar
demo-gateway
Demoproject for Spring Boot
org.springframework.boot
spring-boot-starter-parent
2.1.3.RELEASE
UTF-8
UTF-8
1.8
Greenwich.SR1
1.2.58
1.6.0.RELEASE
0.0.3
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-gateway
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
javax.servlet
servlet-api
2.5
org.springframework
spring-web
5.1.8.RELEASE
compile
com.alibaba
fastjson
1.2.58
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
spring-milestones
Spring Milestones
https://repo.spring.io/milestone
false
新建一个GlobalFilter
注:这里实现的是1、对response的重新包装,意思就是可以更改响应体为自己想要的样子。2、如果是登陆请求,生成token存入Redis,如果是登出,直接踢掉Redis上的token,如果是其他操作的话,返回 用户信息给前端。
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang.StringEscapeUtils;
import org.reactivestreams.Publisher;
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.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
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.Charset;
/**
* @ClassName WrapperResponseGlobalFilter
* @Description 全局过滤器,用来修改response和处理数据
**/
@Component
public class WrapperResponseGlobalFilter implements GlobalFilter, Ordered {
TokenUtil tokenUtil = new TokenUtil();
RedisUtil redisUtil = new RedisUtil();
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse originalResponse = exchange.getResponse();
ServerHttpRequest originalRequest = exchange.getRequest();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
// 登陆请求路径
String loginPath = originalRequest.getPath().toString();
// 定义新的消息头
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getResponse().getHeaders());
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono writeWith(Publisher extends DataBuffer> body) {
if (body instanceof Flux) {
Flux extends DataBuffer> fluxBody = (Flux extends DataBuffer>) body;
return super.writeWith(fluxBody.map(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
// 释放掉内存
DataBufferUtils.release(dataBuffer);
// 请求体数据
String data = "";
// 用户名
String loginName = "";
String token = "";
byte[] b = new byte[100];
String s1 ;
// 请求的json数据(可修改)
String s = new String(content, Charset.forName("UTF-8"));
// s 是response 的值,在此进行修改返回
HttpHeaders headers = getDelegate().getHeaders();
byte[] uppedContent = new String(b, Charset.forName("UTF-8")).getBytes();
return bufferFactory.wrap(uppedContent);
}));
}
// 如果body不是flux,不走这里
return super.writeWith(body);
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
//由于修改了请求体的body,导致content-length长度不确定,因此使用分块编码
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
return httpHeaders;
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
@Override
public int getOrder() {
return -2;
}
}
新建 LoginGatewayFilterFactory
注:处理一些其他不是很重要的操作,比如:转发请求之后,打印日志等操作。
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
/**
* @ClassName LoginGatewayFilterFactory
* @Description 登陆过滤器
* @Date 2019/8/26 11:54
**/
public class LoginGatewayFilterFactory extends AbstractGatewayFilterFactory {
private static final Log log = LogFactory.getLog(GatewayFilter.class);
private static final String LOGIN_BEGIN = "loginBegin";
private static final String KEY = "withParams";
@Override
public List shortcutFieldOrder() {
return Arrays.asList(KEY);
}
public LoginGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
exchange.getAttributes().put(LOGIN_BEGIN, System.currentTimeMillis());
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute(LOGIN_BEGIN);
if (startTime != null) {
StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
.append(": ")
.append(System.currentTimeMillis() - startTime)
.append("ms");
if (config.isWithParams()) {
sb.append(" params:").append(exchange.getRequest().getQueryParams());
}
log.info(sb.toString());
}
})
);
};
}
}
如果对于SpringCloud和Gateway有什么疑问的话,也欢迎一起来讨论。
我是进阶的球儿,大家一起2019年的爬坑历程。感觉分享很给力的话给个赞,谢谢!!!有问题也可以下方留言沟通。