feat(master): SpringCLoud Predicate

Predicate 相关的逻辑代码更新
master
土豆兄弟 2 months ago
parent 4c744ab351
commit eed300274c

@ -0,0 +1,41 @@
# SpringCloud - Gateway
## 1. 基础介绍
- SpringCloud Gateway 的核心概念
- SpringCloud Gateway 是 Spring 官方最新推出的一款基于 SpringFramework 5, Project Reactor 和SpringBoot 2之上开发的网关
- 它与第一代网关 Zuu 不同的是: gateway 是异步非阻塞的(netty + webflux 实现), zuul是同步阻塞请求的
- Gateway 三大组成部分
- Route 路由(ID + 目标URL) - Predicate 断言 - Filter 过滤器
- SpringCloud Gateway 的工作模型
- SpringCloud Gateway 工作模型图示及解读
- 请求发送到网关,经由分发器将请求匹配到相应的 HandlerMapping
- 请求和处理器之间有一个映射,路由到网关处理程序,即 Web Handler
- 执行特定的请求过滤器链 (Filters Proxy Filter)
- 最终到达代理的微服务 (Proxied Service)
---
## 2. 谓词 Predicate 的原理与应用
- 参考: [PredicateTest.java]
## 3. 集成 Nacos 实现动态路由配置
- 静态路由配置
- 静态路由配置写在配置文件中(yml或者 properties 文件中),端点是:spring.cloud.gateway
- 缺点非常明显,每次改动都需要网关模块重新部署
---
## 4. 注册网关事件监听器
- 参考: [DynamicRouteServiceImpl.java] | [DynamicRouteServiceImplByNacos.java]
- 验证网关监听器的可用性 修改看是否可以生效, 已经测试成功
---
## 5. 解读 SpringCloud Gateway Filter

@ -1,3 +0,0 @@
# SpringCloud - Gateway
- 对 SpringCloud - Gateway 进行研究

@ -11,6 +11,8 @@
</parent>
<artifactId>dev-protocol-springcloud-gateway</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
@ -18,4 +20,74 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!-- 模块名及描述信息 -->
<name>e-commerce-gateway</name>
<description>Spring Cloud Gateway</description>
<dependencies>
<!-- 因为spring cloud gateway是基于webflux的如果非要web支持的话需要导入spring-boot-starter-webflux而不是spring-boot-start-web -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- spring cloud alibaba nacos discovery 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</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.imooc.ecommerce</groupId>-->
<!-- <artifactId>e-commerce-common</artifactId>-->
<!-- <version>1.0-SNAPSHOT</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</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>

@ -0,0 +1,17 @@
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* <h1></h1>
* */
@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}

@ -1,7 +0,0 @@
package org.example;
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}

@ -0,0 +1,122 @@
package org.example.conf;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
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.util.CollectionUtils;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* Aware: Service
*
* ApplicationEventPublisherAware
* */
@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> routeDefinitionsExits =
routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();
if (!CollectionUtils.isEmpty(routeDefinitionsExits)) {
// 清除掉之前所有的 "旧的" 路由定义
routeDefinitionsExits.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 "delete 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();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
} catch (Exception ex) {
return "update route fail";
}
}
}

@ -0,0 +1,127 @@
package org.example.conf;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.common.utils.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.Executor;
import java.util.List;
import java.util.Properties;
/**
* <h1> nacos , Nacos </h1>
* */
@Slf4j
@Component
@DependsOn({"gatewayConfig"}) // 依赖于 GatewayConfig 这个 Bean , 注意要首字母小写
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> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
if (CollectionUtils.isNotEmpty(definitionList)) {
for (RouteDefinition definition : definitionList) {
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> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
log.info("update route: [{}]", definitionList.toString());
dynamicRouteService.updateList(definitionList);
}
});
} catch (NacosException ex) {
log.error("dynamic update gateway config error: [{}]", ex.getMessage(), ex);
}
}
}

@ -0,0 +1,46 @@
package org.example.conf;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
/**
* <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;
}
}

@ -0,0 +1,54 @@
server:
port: 9001
servlet:
context-path: /dev-protocol-springcloud-gateway
spring:
application:
name: dev-protocol-springcloud-gateway
cloud:
nacos:
discovery:
enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
server-addr: 127.0.0.1:8848 # Nacos 服务器地址
namespace: 1ccc74ae-9398-4dbe-b9d7-4f9addf9f40c
metadata:
management:
context-path: ${server.servlet.context-path}/actuator
# 静态路由
# gateway:
# routes:
# - id: path_route # 路由的ID
# uri: 127.0.0.1:8080/user/{id} # 匹配后路由地址
# predicates: # 断言, 路径相匹配的进行路由
# - Path=/user/{id}
# kafka:
# bootstrap-servers: 127.0.0.1:9092
# producer:
# retries: 3
# consumer:
# auto-offset-reset: latest
# zipkin:
# sender:
# type: kafka # 默认是 web
# base-url: http://localhost:9411/
main:
allow-bean-definition-overriding: true # 因为将来会引入很多依赖, 难免有重名的 bean
# 这个地方独立配置, 是网关的数据, 代码 GatewayConfig.java 中读取被监听
nacos:
gateway:
route:
config:
data-id: dev-protocol-springcloud-gateway-router
group: dev-protocol
# 暴露端点
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always

@ -0,0 +1,17 @@
### 登录
POST 127.0.0.1:9001/imooc/e-commerce/login
Content-Type: application/json
{
"username": "Qinyi@imooc.com",
"password": "25d55ad283aa400af464c76d713c07ad"
}
###
POST 127.0.0.1:9001/imooc/e-commerce/register
Content-Type: application/json
{
"username": "ImoocQinyi@imooc.com",
"password": "25d55ad283aa400af464c76d713c07ad"
}

@ -0,0 +1,5 @@
### 查询服务
GET http://127.0.0.1:9001/imooc/ecommerce-nacos-client/nacos-client/service-instance?serviceId=e-commerce-gateway
Accept: application/json
e-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjE3LFwidXNlcm5hbWVcIjpcIkltb29jUWlueWlAaW1vb2MuY29tXCJ9IiwianRpIjoiMGIxNzQyYWItMWU3OC00OTZjLWIyNTAtMjNkZGQ1ZGEyZTU1IiwiZXhwIjoxNjI0MjA0ODAwfQ.QKGHzohSHdYDHzUVHpe9gNPUgzfkPwrSbB-WiMWYjLlt2tr9BufzZM8bSt-whb_bd0hKoC6rkYYO0WUVR67uSML-2yaTL1xMIn8GH9Flyig3rpO4vefL3Hp2TXIpwHHa7WlKsLzcUpNk9lxWs2B5k0ICdYCH_jD5Tx6N7CzfSUG9u4fOnVeM9UFE2nX_DURupUh_DKCc2oOoMeyCSR7Ma8-Ab4WQU3r-U0YivR8G1A0kmKOIoTeRhM3LcPuxUPh3rCbrjzMg--fexRGw0O38Qsby6pz-ku2IlTyFXY6_jNOG1BZR34-jBOnaIciP1TExw9bFumeuC2GcowTHJVH1Nw
token: imooc

@ -0,0 +1,18 @@
package org.example;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* <h1></h1>
* */
@SpringBootTest
@RunWith(SpringRunner.class)
public class GatewayApplicationTest {
@Test
public void contextLoad() {
}
}

@ -0,0 +1,83 @@
package org.example.service;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
import java.util.List;
import java.util.function.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(), : 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);
}
}

@ -25,11 +25,19 @@
</properties>
<dependencies>
<!-- 在springboot环境下开发web项目需要引入springMvc的相关启动依赖,加上后发现服务成功注册到了nacos注册中心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 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.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- <dependency>
<groupId>com.imooc.ecommerce</groupId>
@ -47,10 +55,7 @@
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- &lt;!&ndash; zipkin = spring-cloud-starter-sleuth + spring-cloud-sleuth-zipkin&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->

@ -0,0 +1,51 @@
package org.example.controller;
import lombok.extern.slf4j.Slf4j;
import org.example.service.NacosClientService;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* <h1>nacos client controller</h1>
* */
@Slf4j
@RestController
@RequestMapping("/nacos-client")
public class NacosClientController {
private final NacosClientService nacosClientService;
// private final ProjectConfig projectConfig;
public NacosClientController(NacosClientService nacosClientService
// ProjectConfig projectConfig
) {
this.nacosClientService = nacosClientService;
// this.projectConfig = projectConfig;
}
/**
* <h2> service id </h2>
* */
@GetMapping("/service-instance")
public List<ServiceInstance> logNacosClientInfo(
@RequestParam(defaultValue = "dev-protocol-spring-cloud-nacos") String serviceId) {
log.info("coming in log nacos client info: [{}]", serviceId);
return nacosClientService.getNacosClientInfo(serviceId);
}
/* *//**
* <h2> Nacos </h2>
* *//*
@GetMapping("/project-config")
public ProjectConfig getProjectConfig() {
return projectConfig;
}*/
}

@ -1,11 +1,11 @@
server:
port: 8000
servlet:
context-path: /e-commerce-nacos-client
context-path: /dev-protocol-spring-cloud-nacos
spring:
application:
name: e-commerce-nacos-client # 应用名称也是构成 Nacos 配置管理 dataId 字段的一部分 (当 config.prefix 为空时)
name: dev-protocol-spring-cloud-nacos # 应用名称也是构成 Nacos 配置管理 dataId 字段的一部分 (当 config.prefix 为空时)
cloud:
nacos:
# 服务注册发现
@ -64,11 +64,11 @@ spring:
# enabled: true
# 暴露端点
#management:
# endpoints:
# web:
# exposure:
# include: '*'
# endpoint:
# health:
# show-details: always
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always

@ -1,13 +1,13 @@
### 查询服务实例信息
GET http://127.0.0.1:8000/ecommerce-nacos-client/nacos-client/service-instance
GET http://127.0.0.1:8000/dev-protocol-spring-cloud-nacos/nacos-client/service-instance
Accept: application/json
### 动态从 Nacos Server 中获取配置信息
GET http://127.0.0.1:8000/ecommerce-nacos-client/nacos-client/project-config
GET http://127.0.0.1:8000/dev-protocol-spring-cloud-nacos/nacos-client/project-config
Accept: application/json
### 查看 Sleuth 跟踪信息
GET http://127.0.0.1:9001/imooc/ecommerce-nacos-client/sleuth/trace-info
GET http://127.0.0.1:9001/imooc/dev-protocol-spring-cloud-nacos/sleuth/trace-info
Accept: application/json
e-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjE3LFwidXNlcm5hbWVcIjpcIkltb29jUWlueWlAaW1vb2MuY29tXCJ9IiwianRpIjoiMGIxNzQyYWItMWU3OC00OTZjLWIyNTAtMjNkZGQ1ZGEyZTU1IiwiZXhwIjoxNjI0MjA0ODAwfQ.QKGHzohSHdYDHzUVHpe9gNPUgzfkPwrSbB-WiMWYjLlt2tr9BufzZM8bSt-whb_bd0hKoC6rkYYO0WUVR67uSML-2yaTL1xMIn8GH9Flyig3rpO4vefL3Hp2TXIpwHHa7WlKsLzcUpNk9lxWs2B5k0ICdYCH_jD5Tx6N7CzfSUG9u4fOnVeM9UFE2nX_DURupUh_DKCc2oOoMeyCSR7Ma8-Ab4WQU3r-U0YivR8G1A0kmKOIoTeRhM3LcPuxUPh3rCbrjzMg--fexRGw0O38Qsby6pz-ku2IlTyFXY6_jNOG1BZR34-jBOnaIciP1TExw9bFumeuC2GcowTHJVH1Nw
token: imooc

Loading…
Cancel
Save