spring cloud alibaba开发笔记五(Gateway)
SpringCloud Gateway
◆SpringCloud Gateway是Spring官方最新推出的一款基于SpringFramework 5 ,Project Reactor和SpringBoot 2之上开发的网关
◆它与第- -代网关Zuul不同的是: gateway是异步非阻塞的( netty + webflux实现) ;zuul是同步阻塞请求的
◆Gateway 三大组成部分:
1.Route路由:ID、目标URL
2.Predicate 断言
3.Filter 过滤器
SpringCloud Gateway工作模型图示及解读:
◆请求发送到网关,经由分发器将请求匹配到相应的HandlerMapping
◆请求和处理器之间有一个映射,路由到网关处理程序,即Web Handler
◆执行特定的请求过滤器链
◆最终到达代理的微服务
创建gateway子模块
pom示例
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>e-commerce-springcloud</artifactId>
<groupId>com.taluohui.ecommerce</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>e-commerce-gateway</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 模块名及描述信息 -->
<name>e-commerce-gateway</name>
<description>Spring Cloud Gateway</description>
<dependencies>
<!-- spring cloud alibaba nacos discovery 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- zipkin = spring-cloud-starter-sleuth + spring-cloud-sleuth-zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.5.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.taluohui.ecommerce</groupId>
<artifactId>e-commerce-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<!--
SpringBoot的Maven插件, 能够以Maven的方式为应用提供SpringBoot的支持,可以将
SpringBoot应用打包为可执行的jar或war文件, 然后以通常的方式运行SpringBoot应用
-->
<build>
<finalName>${artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
配置文件bootstrap.yml
server:
port: 9000
servlet:
context-path: /shuai
spring:
application:
name: e-commerce-gateway
cloud:
nacos:
discovery:
enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
server-addr: 1.15.247.9:8848 # Nacos 服务器地址
namespace: 22d40198-8462-499d-a7fe-dbb2da958648
metadata:
management:
context-path: ${server.servlet.context-path}/actuator
main:
allow-bean-definition-overriding: true # 因为将来会引入很多依赖, 难免有重名的 bean
# 这个地方独立配置, 是网关的数据, 代码 GatewayConfig.java 中读取被监听
nacos:
gateway:
route:
config:
data-id: e-commerce-gateway-router
group: e-commerce
# 暴露端点
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
启动类,网关启动入口
/**
* <h1>网关启动入口</h1>
*/
@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
Java8 Predicate 使用方法与思想
/**
* <h1>Java8 Predicate 使用方法与思想</h1>
*/
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class PredicateTest {
public static List<String> MICRO_SERVICE = Arrays.asList(
"nacos", "authority", "gateway", "ribbon", "feign", "hystrix", "e-commerce"
);
/**
* <h2>test 方法主要用于参数符不符合规则,返回值是Boolean</h2>
*/
@Test
public void testPredicateTest() {
Predicate<String> letterLengthLimit = s -> s.length() > 5;
MICRO_SERVICE.stream().filter(letterLengthLimit).forEach(System.out::println);
}
/**
* <h2>and 方法等同于我们的逻辑与 &&, 存在短路特性,需要所有条件都满足</h2>
*/
@Test
public void testPredicateAnd() {
Predicate<String> letterLengthLimit = s -> s.length() > 5;
Predicate<String> letterStartWith = s -> s.startsWith("gate");
MICRO_SERVICE.stream().filter(
letterLengthLimit.and(letterStartWith)
).forEach(System.out::println);
}
/**
* <h2>or 方法等同于我们的逻辑或 ||, 多个条件只要一个满足</h2>
*/
@Test
public void testPredicateOr() {
Predicate<String> letterLengthLimit = s -> s.length() > 5;
Predicate<String> letterStartWith = s -> s.startsWith("gate");
MICRO_SERVICE.stream().filter(
letterLengthLimit.or(letterStartWith)
).forEach(System.out::println);
}
/**
* <h2>negate 方法等同于我们的逻辑非 !</h2>
*/
@Test
public void testPredicateNegate() {
Predicate<String> letterStartWith = s -> s.startsWith("gate");
MICRO_SERVICE.stream().filter(letterStartWith.negate()).forEach(System.out::println);
}
/**
* <h2>isEqual 类似于equals(),区别在于:isEqual会先判断对象是否为NULL,
* 不为NULL再使用equals进行比较</h2>
*/
@Test
public void testPredicateIsEqual() {
Predicate<String> equalGateway = s -> Predicate.isEqual("gateway").test(s);
MICRO_SERVICE.stream().filter(equalGateway).forEach(System.out::println);
}
}
集成AlibabaNacos实现动态路由配置
静态路由配置
静态路由配置,写在配置文件中端点:spring.cloud.gateway
缺点: 每次改动都需要网关模块重新部署
# 静态路由
gateway:
routes:
- id: path_route # 路由的ID
uri: 127.0.0.1:8080/user/{id} # 匹配后路由地址
predicates: # 断言, 路径相匹配的进行路由
- Path=/user/{id}
动态路由配置
在nacos界面的配置列表进行配置的添加:
选择JSON格式,示例配置内容,这是nacos-client微服务部分的配置,以后的配置可以模仿这个。
[
{
"id": "e-commerce-nacos-client",
"predicayes": [
{
"args": {
"pattern": "/shuai/ecommerce-nacos-client/**"
},
"name": "Path"
}
],
"uri": "lb://e-commerce-nacos-client",
"filters": [
{
"name": "HeaderToken"
},
{
"name": "StripPrefix",
"args": {
"parts": "1"
}
}
]
}
]
配置类,读取Nacos相关的配置项
/**
* <h1>配置类,读取Nacos相关的配置项,用于配置监听器</h1>
*/
@Configuration
public class GatewayConfig {
/** 读取配置的超时时间 */
public static final long DEFAULT_TIMEOUT = 30000;
/** Nacos 服务器地址 */
public static String NACOS_SERVER_ADDR;
/** 命名空间 */
public static String NACOS_NAMESPACE;
/** Data-id */
public static String NACOS_ROUTE_DATA_ID;
/** 分组 id */
public static String NACOS_ROUTE_GROUP;
@Value("${spring.cloud.nacos.discovery.server-addr}")
public void setNacosServerAddr(String nacosServerAddr) {
NACOS_SERVER_ADDR = nacosServerAddr;
}
@Value("${spring.cloud.nacos.discovery.namespace}")
public void setNacosNamespace(String nacosNamespace) {
NACOS_NAMESPACE = nacosNamespace;
}
@Value("${nacos.gateway.route.config.data-id}")
public void setNacosRouteDataId(String nacosRouteDataId) {
NACOS_ROUTE_DATA_ID = nacosRouteDataId;
}
@Value("${nacos.gateway.route.config.group}")
public void setNacosRouteGroup(String nacosRouteGroup) {
NACOS_ROUTE_GROUP = nacosRouteGroup;
}
}
事件推送 Aware:动态更新路由网关 Service
/**
* 事件推送 Aware:动态更新路由网关 Service
*/
@Slf4j
@Service
@SuppressWarnings("all")
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
/** 写路由定义 */
private final RouteDefinitionWriter routeDefinitionWriter;
/** 获取路由定义 */
private final RouteDefinitionLocator routeDefinitionLocator;
/** 事件发布 */
private ApplicationEventPublisher publisher;
public DynamicRouteServiceImpl(RouteDefinitionWriter routeDefinitionWriter, RouteDefinitionLocator routeDefinitionLocator) {
this.routeDefinitionWriter = routeDefinitionWriter;
this.routeDefinitionLocator = routeDefinitionLocator;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
//完成事件推送句柄的初始化
this.publisher = applicationEventPublisher;
}
/**
*<h2>增加路由定义</h2>
*/
public String addRouteDefinition(RouteDefinition definition) {
log.info("gateway add route: [{}]", definition);
//保存路由配置并发布
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
//发布事件通知给Gateway,同步新增的路由定义
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}
/**
*<h2>更新路由</h2>
*/
public String updateList(List<RouteDefinition> definitions) {
log.info("gateway update route: [{}]", definitions);
//先拿到当前 Gateway 中存储的路由定义
List<RouteDefinition> routefinitionsExits =
routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();
if(!CollectionUtils.isEmpty(routefinitionsExits)) {
//清除之前所有的“旧的”路由定义
routefinitionsExits.forEach(rd -> {
log.info("delete route definition: [{}]", rd);
deleteById(rd.getId());
});
}
//把更新的路由定义同步到 gateway 中
definitions.forEach(definition -> updateByRouteDefinition(definition));
return "success";
}
/**
*<h2>根据路由 id 删除路由配置</h2>
*/
private String deleteById(String id) {
try {
log.info("gateway delete route id: [{}]", id);
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
//发布事件通知给Gateway,更新路由定义
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
} catch (Exception ex) {
log.error("gateway delete route fail: [{}]", ex.getMessage(), ex);
return "delete fail";
}
}
/**
*<h2>更新路由</h2>
* 更新路由就是: 删除 + 新增
*/
private String updateByRouteDefinition(RouteDefinition definition) {
try {
log.info("gateway update route: [{}]", definition);
this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
} catch (Exception ex) {
return "update fail, not find route routeId: " + definition.getId();
}
try {
this.routeDefinitionWriter.save(Mono.just(definition)).subscribe();
//发布事件通知给Gateway,更新路由定义
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
} catch (Exception ex) {
return "update route find";
}
}
}
通过 nacos 下发动态路由配置,监听 Nacos 中路由配置变更
/**
* <h1>通过 nacos 下发动态路由配置,监听 Nacos 中路由配置变更</h1>
*/
@Slf4j
@Component
/** 依赖gatewayConfig类,在其加载之后加载 */
@DependsOn({"gatewayConfig"})
public class DynamicRouteServiceImplByNacos {
/** Nacos 配置服务 */
private ConfigService configService;
private final DynamicRouteServiceImpl dynamicRouteService;
public DynamicRouteServiceImplByNacos(DynamicRouteServiceImpl dynamicRouteService) {
this.dynamicRouteService = dynamicRouteService;
}
/**
* <h2>Bean 在容器中构造完成之后会执行 init 方法</h2>
*/
@PostConstruct
public void init() {
log.info("gateway route init....");
try {
//初始化 Nacos 配置客户端
configService = initConfigService();
if(null == configService) {
log.error("init config service fail");
return;
}
//通过 Nacos Config 并指定路由配置路径去获取路由配置
String configInfo = configService.getConfig(
GatewayConfig.NACOS_ROUTE_DATA_ID,
GatewayConfig.NACOS_ROUTE_GROUP,
GatewayConfig.DEFAULT_TIMEOUT
);
log.info("get current gateway config: [{}]", configInfo);
List<RouteDefinition> definitions =
JSON.parseArray(configInfo, RouteDefinition.class);
if(CollectionUtils.isNotEmpty(definitions)) {
for (RouteDefinition definition : definitions) {
log.info("init gateway config: [{}]", definition.toString());
dynamicRouteService.addRouteDefinition(definition);
}
}
} catch (Exception ex) {
log.error("gateway route init has some error: [{}]", ex.getMessage(), ex);
}
//设置监听器
dynamicRouteByNacosListener(GatewayConfig.NACOS_ROUTE_DATA_ID,
GatewayConfig.NACOS_ROUTE_GROUP);
}
/**
* <h2> 初始化 Nacos Config </h2>
*/
private ConfigService initConfigService() {
try {
Properties properties = new Properties();
properties.setProperty("serverAddr", GatewayConfig.NACOS_SERVER_ADDR);
properties.setProperty("namespace", GatewayConfig.NACOS_NAMESPACE);
return configService = NacosFactory.createConfigService(properties);
} catch (Exception ex) {
log.error("init gateway nacos config error: [{}]", ex.getMessage(), ex);
return null;
}
}
/**
* <h2>监听 Nacos下发的动态路由配置</h2>
*/
private void dynamicRouteByNacosListener(String dataId, String group) {
try {
// 给 Nacos Config 客户端增加一个监听器
configService.addListener(dataId, group, new Listener() {
/**
* <h2>自己提供线程池执行操作,不提供使用默认就行</h2>
*/
@Override
public Executor getExecutor() {
return null;
}
/**
* <h2>监听器收到配置更新</h2>
* @param configInfo Nacos 中最新的配置定义
*/
@Override
public void receiveConfigInfo(String configInfo) {
log.info("start to update config: [{}]", configInfo);
List<RouteDefinition> definitions = JSON.parseArray(configInfo, RouteDefinition.class);
log.info("update route: [{}]", definitions.toString());
dynamicRouteService.updateList(definitions);
}
});
} catch (NacosException ex) {
log.error("dynamic update gateway config error: [{}]", ex.getMessage(), ex);
}
}
}
最后启动,可以成功读取到nacos上的配置,并且在nacos上更改配置之后,gateway会自动更新配置。
SpringCloud Gateway Filter过滤器
◆SpringCloud Gateway基于过滤器实现,同zuul类似,有pre和post两种方式的filter ,分别处理前置逻辑和后置逻辑
◆客户端的请求先经过 pre类型的filter,然后将请求转发到具体的业务服务 ,收到业务服务的响应之后,再经过post类型的filter处理,最后返回响应到客户端
◆Filter -共有两大类:全局过滤器和局部过滤器
全局过滤器:RouteToRequestUrlFilter
实现接口GlobalFilter,Ordered
局部过滤器:PrefixPathGatewayFilterFactory(前缀局部过滤器)增加相应的前缀
StripPrefixGatewayFilterFactory(后缀局部过滤器)去掉相应的前缀
实现接口GatewayFilter,Ordered
HTTP 请求头部携带 Token 验证过滤器(局部过滤器)
新建filter文件夹,在里面添加,这只是演示,并不具备实际项目意义
/**
* <h1>HTTP 请求头部携带 Token 验证过滤器</h1>
*/
public class HeaderTokenGatewayFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//从 HTTP Header 中寻找 key 为 token, value 为 imooc 的键值对
String name = exchange.getRequest().getHeaders().getFirst("token");
if("shuai".equals(name)) {
return chain.filter(exchange);
}
//标记此次请求没有权限,并结束这次请求
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE + 2;
}
}
由于是局部过滤器,还要实现filter/factory/HeaderTokenAbstractGatewayFilterFactory工厂类
@Component
public class HeaderTokenAbstractGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
@Override
public GatewayFilter apply(Object config) {
return new HeaderTokenGatewayFilter();
}
}
最后还需要在配置文件中进行配置,才会生效。如下在nacos配置管理中的配置
"filters": [
{
"name": "HeaderToken"
},
{
"name": "StripPrefix",
"args": {
"parts": "1"
}
}
缓存请求 body 的全局过滤器(Spring WebFlux)
网关常量定义,新建constant文件夹
/**
* <h1>网关常量定义</h1>
*/
public class GatewayConstant {
/** 登录 uri */
public static final String LOGIN_URI = "/e-commerce/login";
/** 注册 uri */
public static final String REGISTER_URI = "/e-commerce/register";
/** 去授权中心拿到登录 token 的 uri 格式化接口 */
public static final String AUTHORITY_CENTER_TOKEN_URL_FORMAT = "http://%s:%s/ecommerce-authority-center/authority/token";
/** 去授权中心注册拿到 token 的 uri 格式化接口 */
public static final String AUTHORITY_CENTER_REGISTER_URL_FORMAT = "http://%s:%s/ecommerce-authority-center/authority/register";
}
将请求里的body缓存下来,方便后面使用,在filter文件夹下
/**
* <h1>缓存请求 body 的全局过滤器</h1>
* Spring WebFlux
*/
@Slf4j
@Component
@SuppressWarnings("all")
public class GlobalCacheRequestBodyFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
boolean isloginOrRegister =
exchange.getRequest().getURI().getPath().contains(GatewayConstant.LOGIN_URI)
|| exchange.getRequest().getURI().getPath().contains(GatewayConstant.REGISTER_URI);
if(null == exchange.getRequest().getHeaders().getContentType() || !isloginOrRegister) {
return chain.filter(exchange);
}
//DataBufferUtils.join 拿到请求中的数据 --> DataBuffer
return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
//确保数据缓冲区不被释放,必须要DataBufferUtils.retain
DataBufferUtils.retain(dataBuffer);
//defer、just都是去创建数据源, 得到当前数据的副本
Flux<DataBuffer> cachedFlux = Flux.defer(() ->
Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
//重新包装 ServerHttpRequest,重写getBody方法, 能够返回请求数据
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
//将包装之后的 ServerHttpRequest 向下继续传递
return chain.filter(exchange.mutate().request(mutatedRequest).build());
});
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE + 1;
}
}
登录,注册,鉴权全局过滤器
在filter文件中新建过滤器
/**
* <h1>全局登录鉴权过滤器</h1>
* */
@Slf4j
@Component
public class GlobalLoginOrRegisterFilter implements GlobalFilter, Ordered {
/** 注册中心客户端, 可以从注册中心中获取服务实例信息 */
private final LoadBalancerClient loadBalancerClient;
private final RestTemplate restTemplate;
public GlobalLoginOrRegisterFilter(LoadBalancerClient loadBalancerClient,
RestTemplate restTemplate) {
this.loadBalancerClient = loadBalancerClient;
this.restTemplate = restTemplate;
}
/**
* <h2>登录、注册、鉴权</h2>
* 1. 如果是登录或注册, 则去授权中心拿到 Token 并返回给客户端
* 2. 如果是访问其他的服务, 则鉴权, 没有权限返回 401
* */
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 1. 如果是登录
if (request.getURI().getPath().contains(GatewayConstant.LOGIN_URI)) {
// 去授权中心拿 token
String token = getTokenFromAuthorityCenter(
request, GatewayConstant.AUTHORITY_CENTER_TOKEN_URL_FORMAT
);
// header 中不能设置 null
response.getHeaders().add(
CommonConstant.JWT_USER_INFO_KEY,
null == token ? "null" : token
);
response.setStatusCode(HttpStatus.OK);
return response.setComplete();
}
// 2. 如果是注册
if (request.getURI().getPath().contains(GatewayConstant.REGISTER_URI)) {
// 去授权中心拿 token: 先创建用户, 再返回 Token
String token = getTokenFromAuthorityCenter(
request,
GatewayConstant.AUTHORITY_CENTER_REGISTER_URL_FORMAT
);
response.getHeaders().add(
CommonConstant.JWT_USER_INFO_KEY,
null == token ? "null" : token
);
response.setStatusCode(HttpStatus.OK);
return response.setComplete();
}
// 3. 访问其他的服务, 则鉴权, 校验是否能够从 Token 中解析出用户信息
HttpHeaders headers = request.getHeaders();
String token = headers.getFirst(CommonConstant.JWT_USER_INFO_KEY);
LoginUserInfo loginUserInfo = null;
try {
loginUserInfo = TokenParseUtil.parseUserInfoFromToken(token);
} catch (Exception ex) {
log.error("parse user info from token error: [{}]", ex.getMessage(), ex);
}
// 获取不到登录用户信息, 返回 401
if (null == loginUserInfo) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// 解析通过, 则放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE + 2;
}
/**
* <h2>从授权中心获取 Token</h2>
* */
private String getTokenFromAuthorityCenter(ServerHttpRequest request, String uriFormat) {
// service id 就是服务名字, 负载均衡
ServiceInstance serviceInstance = loadBalancerClient.choose(
CommonConstant.AUTHORITY_CENTER_SERVICE_ID
);
log.info("Nacos Client Info: [{}], [{}], [{}]",
serviceInstance.getServiceId(), serviceInstance.getInstanceId(),
JSON.toJSONString(serviceInstance.getMetadata()));
String requestUrl = String.format(
uriFormat, serviceInstance.getHost(), serviceInstance.getPort()
);
UsernameAndPassword requestBody = JSON.parseObject(
parseBodyFromRequest(request), UsernameAndPassword.class
);
log.info("login request url and body: [{}], [{}]", requestUrl,
JSON.toJSONString(requestBody));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
JwtToken token = restTemplate.postForObject(
requestUrl,
new HttpEntity<>(JSON.toJSONString(requestBody), headers),
JwtToken.class
);
if (null != token) {
return token.getToken();
}
return null;
}
/**
* <h2>从 Post 请求中获取到请求数据</h2>
* */
private String parseBodyFromRequest(ServerHttpRequest request) {
// 获取请求体
Flux<DataBuffer> body = request.getBody();
AtomicReference<String> bodyRef = new AtomicReference<>();
// 订阅缓冲区去消费请求体中的数据
body.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
// 一定要使用 DataBufferUtils.release 释放掉, 否则, 会出现内存泄露
DataBufferUtils.release(buffer);
bodyRef.set(charBuffer.toString());
});
// 获取 request body
return bodyRef.get();
}
}
在conf文件夹下创建
/**
* <h1>网关需要注入到容器中的 Bean</h1>
* */
@Configuration
public class GatewayBeanConf {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
两种方式配置网关路由
在nacos界面的配置列表进行配置
在nacos界面的配置列表进行配置的添加:
选择JSON格式,示例配置内容,这是nacos-client微服务部分的配置,以后的配置可以模仿这个。
[
{
"id": "e-commerce-nacos-client",
"predicayes": [
{
"args": {
"pattern": "/shuai/ecommerce-nacos-client/**"
},
"name": "Path"
}
],
"uri": "lb://e-commerce-nacos-client",
"filters": [
{
"name": "HeaderToken"
},
{
"name": "StripPrefix",
"args": {
"parts": "1"
}
}
]
}
]
使用代码的方式配置
这种方法只适合比简单的配置,其实就是路由转发。
/**
* <h1>配置登录请求转发规则</h1>
* */
@Configuration
public class RouteLocatorConfig {
/**
* <h2>使用代码定义路由规则, 在网关层面拦截下登录和注册接口</h2>
* */
@Bean
public RouteLocator loginRouteLocator(RouteLocatorBuilder builder) {
// 手动定义 Gateway 路由规则需要指定 id、path 和 uri
return builder.routes()
.route(
"e_commerce_authority",
r -> r.path(
"/imooc/e-commerce/login",
"/imooc/e-commerce/register"
).uri("http://localhost:9001/")
).build();
}
}
将gateway,nacos,鉴权三个微服务启动,并使用http脚本进行验证
### 登录
POST http://localhost:9000/shuai/e-commerce/login
Content-Type: application/json
{
"username": "shuai",
"password": "25d55ad283aa400af464c76d713c07ad"
}
### 注册
POST http://localhost:9000/shuai/e-commerce/register
Content-Type: application/json
{
"username": "shuaizhale",
"password": "25d55ad283aa400af464c76d713c07ad"
}
### 查询服务
GET http://localhost:9000/shuai/ecommerce-nacos-client/nacos-client/service-instance?serviceId=e-commerce-gateway
Accept: application/json
### 登录获得的JWT Token
e-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEyLFwidXNlcm5hbWVcIjpcInNodWFpemhhbGVcIn0iLCJqdGkiOiI3MTllOTE2Mi1hZGVhLTQ2MzItOWEzNS1hYzM4ZDE0NTcwNDIiLCJleHAiOjE2NDQ5NDA4MDB9.St8LrVs8Ym0etRzjgedCLfu-ZH_-u-fGi6-DQzYJu4yiEj0I5UaHAXE9tiXhSvM6Eu30Nlau7eg3qteLGhHKl8SzWaxhaZ0_Quo7gzU8_8zLZQCrkf_9lm_0mH5FHmTluJWRMxUsYTc0F6Yvf_G-jgY9A3wnM2Y8aFGciSfJZxFJ-DcWnrUrM3LCQhFxAunT3apCJ6Wa4wQKX6tNUsiTDvMJiVC3SIhqlUfB2N2d6cQvhlIUXLkMGU_SzyIzD1X6QZ4nu7LPwcNRRbcgtp_5SKYKvt_Pc7hXhg9-IyhVoGutNTpWVj9e5UbeuNze3iIRvyeclEJvjnEIMGqikJBzog
token: shuai
###