玩轉Spring Cloud之服務注冊發現(eureka)及負載均衡消費(ribbon、feign)


  如果說用Spring Boot+Spring MVC是開發單體應用(或單體服務)的利器,那么Spring Boot+Spring MVC+Spring Cloud將是開發分布式應用(快速構建微服務)的又一法寶,相信大家如果看到我近期總結的《JAVA WEB快速入門》系列文章,對Spring Boot+Spring MVC應該是比較熟悉了吧,從本文開始,一起來熟悉Spring Cloud、玩轉Spring Cloud,至於什么是Spring Cloud?我這里就不再介紹了,網上資源太多了,比如:大話Spring CloudSpringCloud是什么?,當然介紹Spring Cloud系列文章也比較多(比如:https://blog.csdn.net/forezp/article/details/70148833),大家也可以參考,我這里只是結合當前最新的Spring Boot、Spring MVC、Spring Cloud來重新演練一遍,把重要的知識點、遇到的一些坑分享出來,一來是為自己做記錄(所謂“好記性不如爛筆頭”),二來可以避免大家學習時走彎路,又因為介紹Spring Cloud文章實在太多了,故玩轉Spring Cloud系列文章更多的是以把實現的DEMO代碼一步步貼出來,一些組件名詞我就不再詳細解釋了,然后對於涉及的重要知識點及踩坑點進行說明,以便大家可以:知其然還能知其所以然。(注:所有示例代碼均采用IDEA IDE編寫)

一、實現eureka server(注冊中心)

  1.1.通過IDEA來創建一個空的spring boot項目(類型是:maven-archtype-quickstart,這樣最精簡,當然如果你使用webapp項目也是可以,只是認為沒有必要)。

    創建步驟有2種,第一種是使用maven創建: maven->maven-archtype-quickstart,然后手動添加相關的spring boot依賴;第二種是使用spring initializer->填寫項目參數->選擇相關依賴(可直接選擇spring cloud相關依賴,如:eureka,這樣就一步到位,這里全部先不選),最終的初始POM XML如下:

<?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">
  <modelVersion>4.0.0</modelVersion>

  <groupId>cn.zuowenjun.cloud</groupId>
  <artifactId>eurekaserver</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>eurekaserver</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.zuowenjun.cn</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.3.RELEASE</version>
  </parent>



  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>


  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

如上所示(如果不是請改成這樣,如果只是多點依賴沒關系,當然我認為此時只需要這么多的依賴即可,多了也無用),我們只是有spring boot的POM依賴,並沒有spring cloud的相關依賴。

  1.2添加spring cloud相關依賴,如下所示:(添加了dependencyManagement節點,並配置spring-cloud-dependencies pom import依賴,目的是:便於依賴繼承,與parent節點功能類似,添加具體依賴時,若包含在parent中或pom import依賴中則無需版本號,能夠保證組件的一致性,詳見:https://blog.csdn.net/mn960mn/article/details/50894022,相反如果沒有配置spring-cloud-dependencies pom import依賴,則添加具體依賴時需要指定version版本號,而且需要注意各依賴組件間的兼容性問題,如下面我把version注釋掉)

  <dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>Greenwich.RELEASE</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

  <dependencies>
    ... ...其它原有依賴
    
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter</artifactId>
      <!--<version>2.1.0.RELEASE</version>-->
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
      <!--<version>2.1.0.RELEASE</version>-->
    </dependency>

  </dependencies>

  1.3.在resouces目錄下(若沒有請創建,注意設為souces root目錄,方法:右鍵文件夾->Mark directory as->souces root)創建application.yml(或application.properties,本文示例全部使用yml),添加如下配置:

server:
  port: 8800

spring:
  applcation:
    name: demo-eurekaserver

# config detail:https://www.jianshu.com/p/98f4e5f6bca7  or https://blog.csdn.net/wo18237095579/article/details/83276352
eureka:
  instance:
    hostname: eurekaserver1 #實例主機名,集群時需要且唯一
  server:
    enable-self-preservation: true #自我保護,正式環境不要這么做
    eviction-interval-timer-in-ms: 5000 #定期清理失效節點,默認60s
    peer-eureka-nodes-update-interval-ms: 6000 #同步更新節點頻率,默認10min
    renewal-percent-threshold: 0.49 #默認0.85
    response-cache-auto-expiration-in-seconds: 30

  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://localhost:${server.port}/eureka/

1.4.在spring boot 啟動類中添加@EnableEurekaServer即可,如下代碼:

package cn.zuowenjun.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class App 
{
    public static void main( String[] args )
    {
        SpringApplication.run(App.class, args);
    }
}
View Code

整個項目結構如下圖示,啟動后瀏覽地址:http://localhost:8800/,會出現spring eureka的主頁,就表明eureka server成功了。

二、實現service provider(含eureka  client)--服務提供者

【即:具體微服務項目,注冊服務信息,暴露API】,當然也有可能同時是service consumer【服務消費者】,需要遠程調用其它服務

  2.1.參照1.1方式創建一個空的spring boot項目,然后添加spring cloud 相關依賴(這里主要是:eureka-client【實現服務自動發現與注冊】、web【即:springMVC,實現服務API】),POM XML添加配置如下:

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Greenwich.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--當.yml配置不生效時,應添加snakeyaml依賴,但一般spring-boot-starter中默認有此依賴,非spring boot項目需要添加-->
    <dependency>
      <groupId>org.yaml</groupId>
      <artifactId>snakeyaml</artifactId>
      <version>1.23</version>
    </dependency>

  </dependencies>

  2.2.在application.yml文件中添加如下配置(若沒有請參見1.3法創建):注意spring.application.name,這個是服務實例名,注冊及服務消費時均需使用該名稱

server:
  port: 8801

spring:
  application:
    name: helloservice
    ip: localhost #自定義配置,在demo代碼中有用到

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8800/eureka/

  3.3.編寫controller 服務相關代碼,在spring boot啟動類上添加@EnableDiscoveryClient注解,具體完整實現代碼如下:(除了@EnableDiscoveryClient注解,基余代碼與普通的spring MVC項目代碼均相同)

//controller:
package cn.zuowenjun.cloud.controller;

import cn.zuowenjun.cloud.model.Result;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @Value("${spring.application.name}")
    private String serviceName;

    @Value("${spring.application.ip}")
    private String address;

    @Value("${server.port}")
    private String port;

    @Autowired
    DiscoveryClient discoveryClient;


    @GetMapping(value = "/")
    public String index(){
        return "demo service";
    }

    @RequestMapping("/hello")
    public  Object hello(){
        return discoveryClient.getServices();
    }


    @RequestMapping("/info")
    public Result info(){
        Result result = new Result();
        result.setServiceName(serviceName);
        result.setHost(String.format("%s:%s", address, port));
        result.setMessage("hello");
        return result;
    }

    @RequestMapping(value = "/multiply/{a}/{b}")
    public Result multiply(@PathVariable("a") int a,@PathVariable("b") int b){
        Result result = new Result();
        result.setServiceName(serviceName);
        result.setHost(String.format("%s:%s", address, port));
        result.setMessage("ok");
        result.setContent(a * b);
        return result;
    }
}

//model:
package cn.zuowenjun.cloud.model;

public class Result {

    private int code;

    private String message;

    private Object content;

    private String serviceName;

    private String host;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Object getContent() {
        return content;
    }

    public void setContent(Object content) {
        this.content = content;
    }

    public String getServiceName() {
        return serviceName;
    }

    public void setServiceName(String serviceName) {
        this.serviceName = serviceName;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }
}

//App spring boot啟動類:

package cn.zuowenjun.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class App 
{
    public static void main( String[] args )
    {
        SpringApplication.run(App.class, args);
    }
}
View Code

  完成上述步驟后即實現了服務提供者項目,完整項目結構如下圖示,啟動運行http://localhost:8801/multiply/324/561(只需關注這個服務方法,后面服務消費會調用這個方法) ,可以看到正常響應出JSON結果,如:"code":0,"message":"ok","content":181764,"serviceName":"helloservice","host":"localhost:8801"}

為了后面服務消費者能體驗出負載均衡的效果,可以把該項目再以另一個端口(server.port=8802)重新啟動運行一個實例(IDEA啟動多個實例的方法請參見:https://blog.csdn.net/forezp/article/details/76408139,最后不一定要改yml中的port配置,也可以直接在Edit Configuration--> program argements中指定:--server.port=8802即可,原理與直接通過命令:java -jar xxx --server.port=8802類似),這樣就會有兩個服務提供者了,如果查看eureka server主頁(http://localhost:8800/)會在Instances currently registered with Eureka列表中展示出2個服務實例信息,如下圖示:

 

三、實現service consumer(含eureka  client)--服務消費者

【即:需要調用微服務API的項目,相對eureka,service provider來講,就是客戶端,消費方】,當然也有可能是service provider【服務提供者】,暴露服務API給其它微服務項目

  3.0.參照1.1方式創建一個空的spring boot項目,然后添加spring cloud 相關依賴(這里僅先是:eureka-client【實現服務自動發現與注冊】、web【即:springMVC,實現服務API】),POM XML添加配置如下:

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
View Code

  3.1方式一:使用restTemplate+ribbon實現服務消費(負載均衡調用遠程服務)

    3.1.1.在POM XML中添加spring-cloud-starter-netflix-ribbon依賴,如下:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>

    3.1.2.編寫controller相關代碼(含遠程服務調用類HelloService),修改spring boot 啟動類,具體完整實現代碼如下:

//spring boot啟動類:
package cn.zuowenjun.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableDiscoveryClient
@SpringBootApplication
class EurekaclientconsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaclientconsumerApplication.class, args);
    }

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate(){
        return  new RestTemplate();
    }

}

//controller:
package cn.zuowenjun.cloud.controller;

import cn.zuowenjun.cloud.service.HelloRemoteService;
import cn.zuowenjun.cloud.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Autowired
    private  HelloService helloService;


    @RequestMapping("/x")
    public Object multiplyForRestTemplate(@RequestParam int a, @RequestParam int b) {
       return   helloService.multiply(a,b);
    }

}


//HelloService(遠程服務代理類) :
package cn.zuowenjun.cloud.service;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class HelloService {

    @Autowired
    private RestTemplate restTemplate;

    @Value("${spring.application.helloServiceProvider}")
    private String  helloServiceName;

    public Object multiply(int a,int b){
        String url="http://"+ helloServiceName +"/multiply/" + a +"/" + b;
        return restTemplate.getForObject(url,String.class);
    }
}
View Code

 如上代碼中最核心的是:HelloService類,通過這個類遠程調用【消費】注冊在eureka server上對應的服務API,而這個類中最核心的對象是:RestTemplate,而這個又是通過在spring boot啟動類(EurekaclientconsumerApplication)中通過代碼注入到Spring IOC容器中的(當然也可以自定義一個config類然后統一寫BEAN注入的方法),重點請看這個restTemplate Bean注冊方法上面的注解:@LoadBalanced,這個就是實現負載均衡(默認是采用輪詢的負載均衡算法,還有其它的負載均衡Rule),就這么簡單嗎?是的,用起來簡單,但內部實現還是非常復雜的,Ribbon的運行原理詳見:深入理解Ribbon之源碼解析,核心思路是:RestTemplate內部維護了一個被@LoadBalance注解的RestTemplate列表,而這些RestTemplate列表又被添加了LoadBalancerInterceptor攔截器,而LoadBalancerInterceptor內部又使用了LoadBalancerClient,而LoadBalancerClient(實現類:RibbonLoadBalancerClient)具體選擇服務實例的邏輯又由ILoadBalancer來處理,ILoadBalancer通過配置IRule、IPing等信息,向EurekaClient獲取注冊列表的信息,並定時向EurekaClient發送“ping”心跳,進而檢查是否更新了服務列表,最后得到注冊服務實例列表后,ILoadBalancer根據IRule的策略進行負載均衡。

  3.1.3.在application.yml文件中添加如下配置(若沒有請參見1.3法創建):

server:
  port: 8666

spring:
  application:
    name: ribbonclient
    helloServiceProvider: helloservice #自定義配置,指定訪問遠程服務名稱,當然也可以寫死在代碼中

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8800/eureka/ #指向eureka server

完成上述步驟即實現了一個基於Ribbon的負載均衡服務消費者(客戶端)項目。

  3.2方式二:使用feign實現服務消費(負載均衡調用遠程服務調用)

  我們仍然基於3.1節原有項目基礎上實現基於feign的負載均衡服務調用,注意feign的底層仍然使用了Ribbon。當然也可以單獨創一個新的spring boot項目(參照第一節介紹)然后再按下文步驟操作即可。

  3.2.1.在POM XML中添加spring-cloud-starter-openfeign依賴,配置如下:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

  3.2.2.在spring boot啟動類(EurekaclientconsumerApplication)上添加:@EnableFeignClients 注解,然后在cn.zuowenjun.cloud.service包中添加自定義HelloRemoteService,這個就是遠程服務調用接口類(或稱:客戶端代理類【接口】),這個就是與3.1中定義的HelloService作用完全類似,只是實現方式不同而矣,最后在controller中添加一個新的API ACTION方法,以便可以調用HelloRemoteService中的服務方法,完整實現代碼如下:

//spring boot啟動類
package cn.zuowenjun.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients(basePackages = "cn.zuowenjun.cloud.service") // 如果啟動類不在根目錄需要指定basePackages,否則不需要
class EurekaclientconsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaclientconsumerApplication.class, args);
    }

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate(){
        return  new RestTemplate();
    }

}

//HelloRemoteService:

package cn.zuowenjun.cloud.service;


import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

/*
*  bug-refer https://blog.csdn.net/zlh313_01/article/details/80309144
*  bug-refer https://blog.csdn.net/alinyua/article/details/80070890
 */
@FeignClient(name= "helloservice")
public interface HelloRemoteService {

    @RequestMapping("/multiply/{a}/{b}")
    Object  multiply(@PathVariable("a") int a, @PathVariable("b") int b);

}

//controller:

package cn.zuowenjun.cloud.controller;

import cn.zuowenjun.cloud.service.HelloRemoteService;
import cn.zuowenjun.cloud.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Autowired
    private  HelloService helloService;

    @Autowired
    private HelloRemoteService helloRemoteService;

    @RequestMapping("/x")
    public Object multiplyForRestTemplate(@RequestParam int a, @RequestParam int b) {
       return   helloService.multiply(a,b);
    }

    @RequestMapping("/multiply/{a}/{b}")
    public Object multiplyForFeignClient(@PathVariable int a, @PathVariable int b) {
        return helloRemoteService.multiply(a,b);
    }
}
View Code

 如上代碼HelloRemoteService是重點,需要注意:

a.必需是interface,因為@FeignClient注解只能用於interface中,而且很顯然HelloRemoteService 是遠程調用,本地不應有實現的,如果知道原理就更明白這個接口只是為了生成可供restTemplate調用的URL方法而矣;

b.@FeignClient注解的name(別名屬性)或value必填,這個就是需要遠程調用服務的應用名稱【即:表明消費哪個服務】

c.接口中定義的方法應與遠程服務的controller中的方法保持一致(方法簽名,注解),同時注意方法上的一些映射請求的注解,如:@RequestMapping,這些與我們在spring MVC用法相同,但含義卻不相同,spring MVC是指處理請求路徑,而這里是調用請求路徑,這個路徑必需與服務提供者API 的對應的ACITON方法上的保持相同,否則將無法成功發送請求。常見問題及解決辦法可參見:https://blog.csdn.net/zlh313_01/article/details/80309144

3.2.3.application.yml配置與3.1.3配置相同,即保持不變即可,最后啟動項目即可(現在這個項目同時包含了Ribbon與Feign的負載均衡遠程調用服務的方式),通過多次訪問:http://localhost:8666/x?a=數字&b=數字 (基於Ribbon實現)、http://localhost:8666/multiply/數字/數字(基於Feign實現)可以看到遠程調用服務成功(即:消費服務成功)。

FeignClient的運行原理詳見:深入理解Feign之源碼解析,核心思路是:spring boot項目啟動時檢查@EnableFeignClients,若有則掃描被@FeignClient注解接口並注入到spring IOC容器中,然后在請求被@ FeignCleint標注的接口方法時,會通過JDK動態代理來生成具體的RequesTemplate,RequesTemplate又會生成Request,Request交給Client去處理,最后Client被封裝到LoadBalanceClient類,這個類Ribbon中的LoadBalancerClient相同,后面的負載均衡的處理請求相同。

項目結構及遠程調用效果如下圖所示:

  

四、下面分享相關可參考的博文資料鏈接:

Spring Cloud之Eureka服務注冊與發現(概念原理篇)

微服務架構:Eureka參數配置項詳解(轉載)

Spring Cloud Netflix - Eureka Server源碼閱讀

Eureka 參數調優

 

提示:本文相關示例項目代碼已上傳GITHUB,地址如下:

https://github.com/zuowj/learning-demos/tree/master/java/demo-eurekaserver

https://github.com/zuowj/learning-demos/tree/master/java/demo-eurekaclient

https://github.com/zuowj/learning-demos/tree/master/java/demo-eurekaclientconsumer

說明:文中若有不足之處歡迎指出,碼字不易,請多支持,謝謝!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM