深入Java微服務之網(wǎng)關系列3: SpringCloudalibaba gateway詳解(史上最全)

九、服務網(wǎng)關:Gateway

9.1、網(wǎng)關簡介

大家都都知道在微服務架構中,一個系統(tǒng)會被拆分為很多個微服務。那么作為客戶端要如何去調(diào)用這么多的微服務呢?如果沒有網(wǎng)關的存在,我們只能在客戶端記錄每個微服務的地址,然后分別去調(diào)用。
這樣的架構會存在許多的問題:
  1. 客戶端多次請求不同的微服務,增加客戶端代碼或配置編寫的復雜性。

  2. 認證復雜,每個服務都需要獨立認證。

  3. 存在跨域請求,在一定場景下處理相對復雜。

    網(wǎng)關就是為了解決這些問題而生的。所謂的API網(wǎng)關,就是指系統(tǒng)的統(tǒng)一入口,它封裝了應用程序的內(nèi)部結構,為客戶端提供統(tǒng)一服務,一些與業(yè)務本身功能無關的公共邏輯可以在這里實現(xiàn),諸如認證、鑒權、監(jiān)控、路由轉發(fā)等等。

9.2、常用的網(wǎng)關

9.2.1、Ngnix+lua

使用nginx的反向代理和負載均衡可實現(xiàn)對api服務器的負載均衡及高可用。

lua是一種腳本語言,可以來編寫一些簡單的邏輯, nginx支持lua腳本

9.2.2、Kong

基于Nginx+Lua開發(fā),性能高,穩(wěn)定,有多個可用的插件(限流、鑒權等等)可以開箱即用。

他的缺點:

  1. 只支持Http協(xié)議。
  2. 二次開發(fā),自由擴展困難。
  3. 提供管理API,缺乏更易用的管控、配置方式。

9.2.3、Zuul

Netflix開源的網(wǎng)關,功能豐富,使用JAVA開發(fā),易于二次開發(fā)。

他的缺點:
  1. 缺乏管控,無法動態(tài)配置。
  2. 依賴組件較多。
  3. 處理Http請求依賴的是Web容器,性能不如Nginx。

9.2.4、Spring Cloud Gateway

Spring公司為了替換Zuul而開發(fā)的網(wǎng)關服務,SpringCloud alibaba技術棧中并沒有提供自己的網(wǎng)關,我們可以采用Spring Cloud Gateway來做網(wǎng)關

9.3、Gateway簡介

Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技術開發(fā)的網(wǎng)關,它旨在為微服務架構提供一種簡單有效的統(tǒng)一的 API 路由管理方式。它的目標是替代Netflix Zuul,其不僅提供統(tǒng)一的路由方式,并且基于 Filter 鏈的方式提供了網(wǎng)關基本的功能,例如:安全,監(jiān)控和限流。

他的主要功能是:
  1. 進行轉發(fā)重定向。
  2. 在開始的時候,所有類都需要做的初始化操作。
  3. 進行網(wǎng)絡隔離。

9.4、快速入門

需求:通過瀏覽器訪問api網(wǎng)關,然后通過網(wǎng)關將請求轉發(fā)到商品微服務。

9.4.1、基礎版

創(chuàng)建一個api-gateway 模塊,并且導入下面的依賴。

<?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>
    Shop-parent
    <groupId>cn.linstudy</groupId>
    <version>1.0.0</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  api-gateway

  <properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>

  <dependencies>
    <!--gateway網(wǎng)關-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      spring-cloud-starter-gateway
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      lombok
    </dependency>
  </dependencies>

</project>
復制代碼

編寫配置文件

server:
  port: 9000 # 指定網(wǎng)關服務的端口
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes: # 路由數(shù)組[路由 就是指定當請求滿足什么條件的時候轉到哪個微服務]
        - id: product_route # 當前路由的標識, 要求唯一
          uri: http://localhost:8081 # 請求要轉發(fā)到的地址
          order: 1 # 路由的優(yōu)先級,數(shù)字越小級別越高
          predicates: # 斷言(就是路由轉發(fā)要滿足的條件)
            - Path=/product-serv/** # 當請求路徑滿足Path指定的規(guī)則時,才進行路由轉發(fā)
          filters: # 過濾器,請求在傳遞過程中可以通過過濾器對其進行一定的修改
            - StripPrefix=1 # 轉發(fā)之前去掉1層路徑
復制代碼

測試

9.4.2、升級版

我們發(fā)現(xiàn)升級版有一個很大的問題,那就是在配置文件中寫死了轉發(fā)路徑的地址,我們需要在注冊中心來獲取地址。

加入nacos依賴

<?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>
    Shop-parent
    <groupId>cn.linstudy</groupId>
    <version>1.0.0</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  api-gateway

  <properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>

  <dependencies>
    <!--gateway網(wǎng)關-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      spring-cloud-starter-gateway
    </dependency>
    <!--nacos客戶端-->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      spring-cloud-starter-alibaba-nacos-discovery
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      lombok
    </dependency>
  </dependencies>

</project>
復制代碼

在主類上添加注解

@SpringBootApplication
@EnableDiscoveryClient
public class GateWayServerApp {
  public static void main(String[] args) {
    SpringApplication.run(GateWayServerApp.class,args);
  }
}
復制代碼

修改配置文件

server:
  port: 9000
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      discovery:
        locator:
          enabled: true # 讓gateway可以發(fā)現(xiàn)nacos中的微服務
      routes:
        - id: product_route # 路由的名字
          uri: lb://product-service # lb指的是從nacos中按照名稱獲取微服務,并遵循負載均衡策略
          predicates:
            - Path=/product-serv/** # 符合這個規(guī)定的才進行1轉發(fā)
          filters:
            - StripPrefix=1 # 將第一層去掉
復制代碼
我們還可以自定義多個路由規(guī)則。
spring:
  application:
    gateway:
      routes:
        - id: product_route
          uri: lb://product-service 
          predicates:
            - Path=/product-serv/**
          filters:
            - StripPrefix=1
        - id: order_route
          uri: lb://order-service 
          predicates:
            - Path=/order-serv/**
          filters:
            - StripPrefix=1
復制代碼

9.4.3、簡寫版

我們的配置文件無需寫的1那么復雜就可以實現(xiàn)功能,有一個簡寫版。
server:
  port: 9000
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          enabled: true # 讓gateway可以發(fā)現(xiàn)nacos中的微服務
復制代碼
我們發(fā)現(xiàn),就發(fā)現(xiàn)只要按照網(wǎng)關地址/微服務名稱/接口的格式去訪問,就可以得到成功響應。

9.5、Gateway核心架構

9.5.1、基本概念

路由(Route) 是 gateway 中最基本的組件之一,表示一個具體的路由信息載體。主要定義了下面的幾個信息:
  1. id:路由標識符,區(qū)別于其他 Route。
  2. uri:路由指向的目的地 uri,即客戶端請求最終被轉發(fā)到的微服務。
  3. order:用于多個 Route 之間的排序,數(shù)值越小排序越靠前,匹配優(yōu)先級越高。
  4. predicate:斷言的作用是進行條件判斷,只有斷言都返回真,才會真正的執(zhí)行路由。
  5. filter:過濾器用于修改請求和響應信息。
  6. predicate:斷言,用于進行條件判斷,只有斷言都返回真,才會真正的執(zhí)行路由。

9.5.2、執(zhí)行原理

  1. 接收用戶的請求,請求處理器交給處理器映射器,返回執(zhí)行鏈。
  2. 請求處理器去調(diào)用web處理器,在web處理器里面對我們的路徑1進行處理。假設1我們的路徑1是:http://localhost:9000/product-serv/get?id=1 ,根據(jù)配置的路由規(guī)則,上本地找對應的服務信息:product-service對應的主機ip是192.168.10.130。
  3. 根據(jù)1ribbon的負載均衡策略去選擇一個節(jié)點,然后拼接好,將路徑中的product-serv替換成192.168.10.130:8081,如果你配置了filter,那么他還會走filter。
  4. 如果你沒有自定義路由的話,默認Gateway會幫你把第一層去掉。網(wǎng)關端口從此一個/開始到第二個/開始算第一層。

9.6、過濾器

Gateway的過濾器的作用是:是在請求的傳遞過程中,對請求和響應做一些手腳。

Gateway的過濾器的生命周期:
  1. PRE:這種過濾器在請求被路由之前調(diào)用。我們可利用這種過濾器實現(xiàn)身份驗證、在集群中選擇 請求的微服務、記錄調(diào)試信息等。

  2. POST:這種過濾器在路由到微服務以后執(zhí)行。這種過濾器可用來為響應添加標準的HTTP Header、收集統(tǒng)計信息和指標、將響應從微服務發(fā)送給客戶端等。

    Gateway 的Filter從作用范圍可分為兩種: GatewayFilter與GlobalFilter:

  3. GatewayFilter:應用到單個路由或者一個分組的路由上。

  4. GlobalFilter:應用到所有的路由上。

9.6.1、局部過濾器

局部過濾器是針對單個路由的過濾器。他分為內(nèi)置過濾器和自定義過濾器。

9.6.1.1、內(nèi)置過濾器

在SpringCloud Gateway中內(nèi)置了很多不同類型的網(wǎng)關路由過濾器。
9.6.1.1.1、局部過濾器內(nèi)容
過濾器工廠 作用 參數(shù)
AddRequestHeader 為原始請求添加Header Header的名稱及值
AddRequestParameter 為原始請求添加請求參數(shù) 參數(shù)名稱及值
AddResponseHeader 為原始響應添加Header Header的名稱及值
DedupeResponseHeader 剔除響應頭中重復的值 需要去重的Header名稱及去重策略
Hystrix 為路由引入Hystrix的斷路器保護 HystrixCommand 的名稱
FallbackHeaders 為fallbackUri的請求頭中添加具體的異常信息 Header的名稱
PrefixPath 為原始請求路徑添加前綴 前綴路徑
PreserveHostHeader 為請求添加一個preserveHostHeader=true的屬性,路由過濾器會檢查該屬性以決定是否要發(fā)送原始的Host
RequestRateLimiter 用于對請求限流,限流算法為令牌桶 keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus
RedirectTo 將原始請求重定向到指定的URL http狀態(tài)碼及重定向的url
RemoveHopByHopHeadersFilter 為原始請求刪除IETF組織規(guī)定的一系列Header 默認就會啟用,可以通過配置指定僅刪除哪些Header
RemoveRequestHeader 為原始請求刪除某個Header Header名稱
RemoveResponseHeader 為原始響應刪除某個Header Header名稱
RewritePath 重寫原始的請求路徑 原始路徑正則表達式以及重寫后路徑的正則表達式
RewriteResponseHeader 重寫原始響應中的某個Header Header名稱,值的正則表達式,重寫后的值
SaveSession 在轉發(fā)請求之前,強制執(zhí)行WebSession::save操作
secureHeaders 為原始響應添加一系列起安全作用的響應頭 無,支持修改這些安全響應頭的值
SetPath 修改原始的請求路徑 修改后的路徑
SetResponseHeader 修改原始響應中某個Header的值 Header名稱,修改后的值
SetStatus 修改原始響應的狀態(tài)碼 HTTP 狀態(tài)碼,可以是數(shù)字,也可以是字符串
StripPrefix 用于截斷原始請求的路徑 使用數(shù)字表示要截斷的路徑的數(shù)量
Retry 針對不同的響應進行重試 retries、statuses、methods、series
RequestSize 設置允許接收最大請求包的大小。如果請求包大小超過設置的值,則返回 413 Payload Too Large 請求包大小,單位為字節(jié),默認值為5M
ModifyRequestBody 在轉發(fā)請求之前修改原始請求體內(nèi)容 修改后的請求體內(nèi)容
ModifyResponseBody 修改原始響應體的內(nèi)容 修改后的響應體內(nèi)容
9.6.1.1.2、局部過濾器的使用
server:
  port: 9000
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      discovery:
        locator:
          enabled: true # 讓gateway可以發(fā)現(xiàn)nacos中的微服務
      routes:
        - id: product_route # 路由的名字
          uri: lb://product-service # lb指的是從nacos中按照名稱獲取微服務,并遵循負載均衡策略
          predicates:
            - Path=/product-serv/** # 符合這個規(guī)定的才進行1轉發(fā)
          filters:
            - StripPrefix=1 # 將第一層去掉
            - SetStatus=2000 # 這里使用內(nèi)置的過濾器,修改返回狀態(tài)
復制代碼

9.6.1.2、自定義局部過濾器

很多的時候,內(nèi)置過濾器沒辦法滿足我們的需求,這個時候就必須自定義局部過濾器。我們假定一個需求是:統(tǒng)計訂單服務調(diào)用耗時。

編寫一個類,用于實現(xiàn)邏輯

**名稱是有固定格式xxxGatewayFilterFactory**
@Component
public class TimeGatewayFilterFactory extends AbstractGatewayFilterFactory<TimeGatewayFilterFactory.Config> {

  private static final String BEGIN_TIME = "beginTime";

  //構造函數(shù)
  public TimeGatewayFilterFactory() {
    super(TimeGatewayFilterFactory.Config.class);
  }

  //讀取配置文件中的參數(shù) 賦值到 配置類中
  @Override
  public List<String> shortcutFieldOrder() {
    return Arrays.asList("show");
  }

  @Override
  public GatewayFilter apply(Config config) {
    return new GatewayFilter() {
      @Override
      public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        if (!config.show){
          // 如果配置類中的show為false,表示放行
          return chain.filter(exchange);
        }
        exchange.getAttributes().put(BEGIN_TIME, System.currentTimeMillis());
        /**
         *  pre的邏輯
         * chain.filter().then(Mono.fromRunable(()->{
         *     post的邏輯
         * }))
         */
        return chain.filter(exchange).then(Mono.fromRunnable(()->{
          Long startTime = exchange.getAttribute(BEGIN_TIME);
          if (startTime != null) {
            System.out.println(exchange.getRequest().getURI() + "請求耗時: " + (System.currentTimeMillis() - startTime) + "ms");
          }
        }));
      }
    };
  }

  @Setter
  @Getter
  static class Config{
    private boolean show;
  }

}
復制代碼

編寫application.xml

server:
  port: 9000
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      discovery:
        locator:
          enabled: true # 讓gateway可以發(fā)現(xiàn)nacos中的微服務
      routes:
        - id: product_route # 路由的名字
          uri: lb://product-service # lb指的是從nacos中按照名稱獲取微服務,并遵循負載均衡策略
          predicates:
            - Path=/product-serv/** # 符合這個規(guī)定的才進行1轉發(fā)
          filters:
            - StripPrefix=1 # 將第一層去掉
        - id: order_route
          uri: lb://order-service
          predicates:
            - Path=/order-serv/**
          filters:
            - StripPrefix=1
            - Time=true
復制代碼

訪問路徑:http://localhost:9000/order-serv/getById?o=1&pid=1

9.6.2、全局過濾器

全局過濾器作用于所有路由, 無需配置。通過全局過濾器可以實現(xiàn)對權限的統(tǒng)一校驗,安全性驗證等功能。SpringCloud Gateway內(nèi)部也是通過一系列的內(nèi)置全局過濾器對整個路由轉發(fā)進行處理。
開發(fā)中的鑒權邏輯:
  • 當客戶端第一次請求服務時,服務端對用戶進行信息認證(登錄)。
  • 認證通過,將用戶信息進行加密形成token,返回給客戶端,作為登錄憑證。
  • 以后每次請求,客戶端都攜帶認證的token。
  • 服務端對token進行解密,判斷是否有效。
我們來模擬一個需求:實現(xiàn)統(tǒng)一鑒權的功能,我們需要在網(wǎng)關判斷請求中是否包含token且,如果沒有則不轉發(fā)路由,有則執(zhí)行正常邏輯。

編寫全局過濾器

@Component
public class AuthGlobalFilter implements GlobalFilter {

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    String token = exchange.getRequest().getQueryParams().getFirst("token");
    if (StringUtils.isBlank(token)) {
      System.out.println("鑒權失敗");
      exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
      return exchange.getResponse().setComplete();
    }
    return chain.filter(exchange);
  }
}
復制代碼

9.6.3、網(wǎng)關限流

網(wǎng)關是所有請求的公共入口,所以可以在網(wǎng)關進行限流,而且限流的方式也很多,我們本次采用前面學過的Sentinel組件來實現(xiàn)網(wǎng)關的限流。Sentinel支持對SpringCloud Gateway、Zuul等主流網(wǎng)關進行限流。
從1.6.0版本開始,Sentinel提供了SpringCloud Gateway的適配模塊,可以提供兩種資源維度的限流:
  • route維度:即在Spring配置文件中配置的路由條目,資源名為對應的routeId
  • 自定義API維度:用戶可以利用Sentinel提供的API來自定義一些API分組

9.6.3.1、網(wǎng)關集成Sentinel

添加依賴

<dependency>
    <groupId>com.alibaba.csp</groupId>
    sentinel-spring-cloud-gateway-adapter
</dependency>
復制代碼

編寫配置類進行限流

配置類的本質(zhì)是用代碼替代nacos圖形化界面限流。
@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 SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        // Register the block exception handler for Spring Cloud Gateway.
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }
    // 初始化一個限流的過濾器
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }
    //增加對商品微服務的限流
     @PostConstruct
    private void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(new GatewayFlowRule("product_route")
                .setCount(3) // 三次
                .setIntervalSec(1) // 一秒,表示一秒鐘1超過了三次就會限流
        );
        GatewayRuleManager.loadRules(rules);
    }
}
復制代碼

修改限流默認返回格式

如果我們不想在限流的時候返回默認的錯誤,那么就需要自定義錯誤,指定自定義的返回格式。我們只需在類中添加一段配置即可。
@PostConstruct
public void initBlockHandlers() {
    BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
    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).
                body(BodyInserters.fromValue(map));
            }
};
    GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
復制代碼

測試

9.6.3.2、自定義API分組

我們可以發(fā)現(xiàn),上面的這種定義,對整個服務進行了限流,粒度不夠細。自定義API分組是一種更細粒度的限流規(guī)則定義,它可以實現(xiàn)某個方法的細粒度限流。

在Shop-order-server項目中添加ApiController

@RestController
@RequestMapping("/api")
public class ApiController {
    @RequestMapping("/hello")
    public String api1(){
        return "api";
    }
}
復制代碼

在GatewayConfiguration中添加配置

@PostConstruct
private void initCustomizedApis() {
    Set<ApiDefinition> definitions = new HashSet<>();
    ApiDefinition api1 = new ApiDefinition("order_api")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                    add(new ApiPathPredicateItem().setPattern("/order-serv/api/**").                 setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                }});
    definitions.add(api1);
    GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
@PostConstruct
private void initGatewayRules() {
    Set<GatewayFlowRule> rules = new HashSet<>();
    rules.add(new GatewayFlowRule("product_route")
                .setCount(3)
                .setIntervalSec(1)
    );
    rules.add(new GatewayFlowRule("order_api").
                setCount(1).
                setIntervalSec(1));
    GatewayRuleManager.loadRules(rules);
}
復制代碼

測試

直接訪問[http://localhost:8082/api/hello](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8082%2Fapi%2Fhello "http://localhost:8082/api/hello") 是不會發(fā)生限流的,訪問[http://localhost:9000/order-serv/api/hello](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A9000%2Forder-serv%2Fapi%2Fhello "http://localhost:9000/order-serv/api/hello") 就會出現(xiàn)限流了。

作者:XiaoLin_Java
鏈接:https://juejin.cn/post/7001816849826447397
來源:稀土掘金
著作權歸作者所有。商業(yè)轉載請聯(lián)系作者獲得授權,非商業(yè)轉載請注明出處。
微信公眾號【程序員黃小斜】作者是前螞蟻金服Java工程師,專注分享Java技術干貨和求職成長心得,不限于BAT面試,算法、計算機基礎、數(shù)據(jù)庫、分布式、spring全家桶、微服務、高并發(fā)、JVM、Docker容器,ELK、大數(shù)據(jù)等。關注后回復【book】領取精選20本Java面試必備精品電子書。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容