当前位置:   article > 正文

4.Gateway_gatewaycallbackmanager

gatewaycallbackmanager

基本入门

在微服务架构里,服务的粒度被进一步细分,各个业务服务可以被独立的设计、开发、测试、部署和管理。这时,各个独立部署单元可以用不同的开发测试团队维护,可以使用不同的编程语言和技术平台进行设计,这就要求必须使用一种语言和平台无关的服务协议作为各个单元间的通讯方式。
在这里插入图片描述

api网关定义

网关的角色是作为一个 API 架构,用来保护、增强和控制对于 API 服务的访问。

API 网关是一个处于应用程序或服务(提供 REST API 接口服务)之前的系统,用来管理授权、访问控制和流量限制等,这样 REST API 接口服务就被 API 网关保护起来,对所有的调用者透明。因此,隐藏在 API 网关后面的业务系统就可以专注于创建和管理服务,而不用去处理这些策略性的基础设施。
在这里插入图片描述

Spring Cloud Gateway

Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project
Reactor等技术开发的网关,Spring Cloud
Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring
Cloud生态系中的网关,目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。

Spring Cloud Gateway 可以看做是一个 Zuul 1.x 的升级版和代替品,比 Zuul 2 更早的使用 Netty
实现异步 IO,从而实现了一个简单、比 Zuul 1.x 更高效的、与 Spring Cloud 紧密配合的 API 网关。

Spring Cloud Gateway 里明确的区分了 Router 和
Filter,并且一个很大的特点是内置了非常多的开箱即用功能,并且都可以通过 SpringBoot 配置或者手工编码链式调用来使用。

比如内置了 10 种 Router,使得我们可以直接配置一下就可以随心所欲的根据 Header、或者 Path、或者 Host、或者
Query 来做路由。

比如区分了一般的 Filter 和全局 Filter,内置了 20 种 Filter 和 9 种全局
Filter,也都可以直接用。当然自定义 Filter 也非常方便。

引入依赖

<!--gateway 此模块不能引入starter-web-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
server:
  port: 7000
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      # 路由数组 (路由:请求满足什么样的条件的时候转发到哪个微服务上)
      routes:
        #当前路由发的标识,要求唯一,默认是UUID
        - id: product_route
        #请求最终要转发到的地址
          uri: http://localhost:8081
        #路由的优先级,数字越小,优先级越高
          order: 1
        #断言(条件判断,返回值是boolean,转发请求要满足的条件)
          predicates:
            # 当请求路径满足Path指定的规则时,此路由信息才正常转发
            - Path=/service-product/**
        #过滤器(在请求传递过程中 对请求做点手脚)
          filters:
            #在请求转发前去掉一层路径 否则请求路径  localhost:7000/product_serv/product/1 --> http://localhost:8081/product_serv/product/1
            - StripPrefix=1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

结合nacos获取服务信息

导入依赖

<!--nacos 服务发现-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

修改启动类

@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

修改配置

server:
  port: 7000
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      #让gateway从nacos中获取服务信息
      discovery:
        locator:
          enabled: true
      # 路由数组 (路由:请求满足什么样的条件的时候转发到哪个微服务上)
      routes:
        #当前路由发的标识,要求唯一,默认是UUID
        - id: product_route
        #请求最终要转发到的地址
        #lb 从nacos中按照名称获取服务,并遵循负载均衡策略
          uri: lb://service-product
        #路由的优先级,数字越小,优先级越高
          order: 1
        #断言(条件判断,返回值是boolean,转发请求要满足的条件)
          predicates:
            # 当请求路径满足Path指定的规则时,此路由信息才正常转发
            - Path=/product_serv/**
        #过滤器(在请求传递过程中 对请求做点手脚)
          filters:
            #在请求转发前去掉一层路径 否则请求路径  localhost:7000/product_serv/product/1 --> http://localhost:8081/product_serv/product/1
            - StripPrefix=1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

在这里插入图片描述

使用默认配置,则通过访问ip:port/服务名/接口路径

server:
  port: 7000
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      #让gateway从nacos中获取服务信息
      discovery:
        locator:
          enabled: true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在这里插入图片描述

Gateway核心概念

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

断言

用于进行条件判断,只有断言都返回真,才会真正的执行路由

内置路由断言工厂

  • 基于Datetime类型的断言工厂
    • AfterRoutePredicateFactory:接收一个日期参数,判断请求日期是否晚于指定日期
    • BeforeRoutePredicateFactory:接收一个日期参数,判断请求日期是否早于指定日期
    • BetweenRoutePredicateFactory:接收两个个日期参数,判断请求日期是否在指定时间段内
-After=2021-12-31T23:59:59.789+08:00[Asia/Shanghai]
  • 1
  • 基于远程地址的断言工厂
    • RemoteAddrRoutePredicateFactory:接收一个ip地址段,判断请求主机地址是否在地址段中
-RemoteAddr=192.168.1.1/24
  • 1
  • 基于Cookie的断言工厂

CookieRoutePredicateFactory:接收两个参数,cookie名字和一个正则表达式,判断请求cookie是否具有给定名称且值与正则表达式匹配

-Cookie=chocolate,ch.
  • 1
  • 基于Header的断言工厂

HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。判断请求Header是否具有给定名称且值与正则表达式匹配

-Header=X-Request-Id,\d+
  • 1
  • 基于Host的断言工厂

HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。

-Host=**.testhost.org
  • 1
  • 基于Method请求方法的断言工厂

MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配

-Method=GET
  • 1
  • 基于Path请求路径的断言工厂

PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则

-Path=/foo/{segment}
  • 1
  • 基于Query请求参数的断言工厂

QueryRoutePredicateFactory:接收两个参数,请求param和正则表达式,判断请求参数是否具有给定名称且值与正则表达式匹配

-Query=baz,ba.
  • 1
  • 基于路由权重的断言工厂

WeightRoutePredicateFactory:接收一个【组名,权重】,然后对于同一个组内的路由按照权重转发

routes:
  -id: weight_route1
  uri: host1
  predicates:
    -Path=/product/**
    -Weight=group3,1
 -id: weight_route2
  uri: host2
  predicates:
    -Path=/product/**
    -Weight=group3,9    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

自定义路由断言

让age在(min,max)之间的人访问

添加断言配置

server:
  port: 7000
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      #让gateway从nacos中获取服务信息
      discovery:
        locator:
          enabled: true
      # 路由数组 (路由:请求满足什么样的条件的时候转发到哪个微服务上)
      routes:
        #当前路由发的标识,要求唯一,默认是UUID
        - id: product_route
        #请求最终要转发到的地址
        #lb 从nacos中按照名称获取服务,并遵循负载均衡策略
          uri: lb://service-product
        #路由的优先级,数字越小,优先级越高
          order: 1
        #断言(条件判断,返回值是boolean,转发请求要满足的条件)
          predicates:
            # 当请求路径满足Path指定的规则时,此路由信息才正常转发
            - Path=/product_serv/**
            # 自定义断言,限制年龄在18-60之间的人才能访问
            - Age=18,60
        #过滤器(在请求传递过程中 对请求做点手脚)
          filters:
            #在请求转发前去掉一层路径 否则请求路径  localhost:7000/product_serv/product/1 --> http://localhost:8081/product_serv/product/1
            - StripPrefix=1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

自定义age路由断言工厂类,名字必须是 配置+RoutePredicateFactory ,必须继承 AbstractRoutePredicateFactory<配置类

@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {


    public AgeRoutePredicateFactory() {
        super(AgeRoutePredicateFactory.Config.class);
    }

    /**
     * 读取配置文件中的参赛者,负载到配置类的属性上
     */
    @Override
    public List<String> shortcutFieldOrder() {
        //这个为准的顺序必须和配置文件中的值顺序对应
        return Arrays.asList("minAge", "maxAge");
    }

    @Override
    public Predicate<ServerWebExchange> apply(AgeRoutePredicateFactory.Config config) {
        return new Predicate<ServerWebExchange>() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                String ageStr = serverWebExchange.getRequest().getQueryParams().getFirst("age");
                if (StringUtils.isNotBlank(ageStr)) {
                    int age = Integer.parseInt(ageStr);
                    if (age > config.getMinAge() && age < config.getMaxAge()) {
                        return true;
                    }
                    return false;
                }
                return false;
            }
        };
    }

    /**
     * 配置类,用于接收配置文件中对应的参数
     */
    @Data
    @NoArgsConstructor
    public static class Config {
        private int minAge;
        private int maxAge;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

过滤器

在这里插入图片描述

在请求的传递过程中,对请求和响应做一些微调

在Gateway中,Filter的生命周期只有两个:“pre”和“post"

  • PRE:在请求之前被调用,可实现身份验证、集群中选择请求的微服务、记录调试信息等
  • POST:在路由到微服务之后执行,可为响应添加标准的HTTP Header 收集统计信息和指标、将响应从微服务发送给客户端等。

局部过滤器

应用到单个路由或者一个分组的路由上
在这里插入图片描述

自定义过滤器

server:
  port: 7000
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      #让gateway从nacos中获取服务信息
      discovery:
        locator:
          enabled: true
      # 路由数组 (路由:请求满足什么样的条件的时候转发到哪个微服务上)
      routes:
        #当前路由发的标识,要求唯一,默认是UUID
        - id: product_route
        #请求最终要转发到的地址
        #lb 从nacos中按照名称获取服务,并遵循负载均衡策略
          uri: lb://service-product
        #路由的优先级,数字越小,优先级越高
          order: 1
        #断言(条件判断,返回值是boolean,转发请求要满足的条件)
          predicates:
            # 当请求路径满足Path指定的规则时,此路由信息才正常转发
            - Path=/product_serv/**
            # 自定义断言,限制年龄在18-60之间的人才能访问
            - Age=18,60
        #过滤器(在请求传递过程中 对请求做点手脚)
          filters:
            #在请求转发前去掉一层路径 否则请求路径  localhost:7000/product_serv/product/1 --> http://localhost:8081/product_serv/product/1
            - StripPrefix=1
            - Log=true,true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

自定义Log局部过滤器类,名字必须是 配置+GatewayFilterFactory ,必须继承 AbstractGatewayFilterFactory<配置类

@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {


    public LogGatewayFilterFactory() {
        super(LogGatewayFilterFactory.Config.class);
    }

    /**
     * 读取配置文件中参数,赋值到配置类
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("consoleLog", "cacheLog");
    }

    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                if (config.isCacheLog()) {
                    System.out.println("cacheLog---开启");
                }
                if (config.isConsoleLog()) {
                    System.out.println("consoleLog---开启");
                    //请求结束
                    return exchange.getResponse().setComplete();
                }
                //继续向下执行
                return chain.filter(exchange);
            }
        };
    }



    @Data
    @NoArgsConstructor
    public static class Config {
        private boolean consoleLog;
        private boolean cacheLog;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

全局过滤器

应用到所有的路由上
在这里插入图片描述

自定义全局过滤器

实现鉴权

在这里插入图片描述

@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    /**
     *过滤器逻辑
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (!StringUtils.equals("admin",token)){
            System.out.println("认证失败----");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    /**
     *标识当前过滤器的优先级,返回值越小,优先级越高
     */
    @Override
    public int getOrder() {
        return 0;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

结合Sentinel网关限流

在这里插入图片描述
在这里插入图片描述

基于route维度限流

导入依赖

<!--限流-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
@Configuration
public class GatewayConfiguration {
    private final List<ViewResolver> viewResolvers;

    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                ServerCodecConfigurer serverCodecConfigurer){
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    /**
     *初始化一个限流的过滤器
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter(){
        return new SentinelGatewayFilter();
    }

    /**
     *配置初始化限流参数
     */
    @PostConstruct
    public void initGatewayRules(){
        Set<GatewayFlowRule> rules = new HashSet<>();
                                    //资源名称,对应路由id
        rules.add(new GatewayFlowRule("product_route")
                    //限流阈值
                    .setCount(1)
                    //统计时间窗口,单位秒,默认1秒
                    .setIntervalSec(1));
        GatewayRuleManager.loadRules(rules);
    }

    /**
     *配置限流异常处理器
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler(){
        return new SentinelGatewayBlockExceptionHandler(viewResolvers,serverCodecConfigurer);
    }

    /**
     *自定义限流异常页面
     */
    @PostConstruct
    public void initBlockHandlers(){
        BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                Map map = new HashMap<>();
                map.put("code",0);
                map.put("message","接口被限流了");
                return ServerResponse.status(HttpStatus.OK)
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                        .body(BodyInserters.fromObject(map));
            }
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

在这里插入图片描述

基于api维度限流

@Configuration
public class GatewayConfiguration {
    private final List<ViewResolver> viewResolvers;

    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    /**
     * 初始化一个限流的过滤器
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    /**
     * 配置初始化限流参数
     */
    @PostConstruct
    public void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(new GatewayFlowRule("product_api1").setCount(1).setIntervalSec(1));
        rules.add(new GatewayFlowRule("product_api2").setCount(1).setIntervalSec(1));
        GatewayRuleManager.loadRules(rules);
    }

    /**
     * 配置限流异常处理器
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    /**
     * 自定义限流异常页面
     */
    @PostConstruct
    public void initBlockHandlers() {
        BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                Map map = new HashMap<>();
                map.put("code", 0);
                map.put("message", "接口被限流了");
                return ServerResponse.status(HttpStatus.OK)
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                        .body(BodyInserters.fromObject(map));
            }
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }


    /**
     * 自定义api分组
     */
    @PostConstruct
    private void initCustomizedApis() {
        Set<ApiDefinition> definitions = new HashSet<>();
        ApiDefinition api1 = new ApiDefinition("product_api1")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {
            {
                // 以/product_serv/product/api1 开头的请求
                add(new ApiPathPredicateItem().setPattern("/product_serv/product/api1/**")
                        .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
            }});

        ApiDefinition api2 = new ApiDefinition("product_api2")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {
                    {
                        // 以/product_serv/product/api2/demo1 完成的url路径匹配
                        add(new ApiPathPredicateItem().setPattern("/product_serv/product/api2/demo1"));
                    }});

        definitions.add(api1);
        definitions.add(api2);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/301947
推荐阅读
相关标签
  

闽ICP备14008679号