Micronaut教程:如何使用基于JVM的框架構(gòu)建微服務(wù)?

本文要點(diǎn):

創(chuàng)新互聯(lián)是一家集成都網(wǎng)站制作、網(wǎng)站建設(shè)、外貿(mào)網(wǎng)站建設(shè)、網(wǎng)站頁(yè)面設(shè)計(jì)、網(wǎng)站優(yōu)化SEO優(yōu)化為一體的專業(yè)的建站公司,已為成都等多地近百家企業(yè)提供網(wǎng)站建設(shè)服務(wù)。追求良好的瀏覽體驗(yàn),以探求精品塑造與理念升華,設(shè)計(jì)最適合用戶的網(wǎng)站頁(yè)面。 合作只是第一步,服務(wù)才是根本,我們始終堅(jiān)持講誠(chéng)信,負(fù)責(zé)任的原則,為您進(jìn)行細(xì)心、貼心、認(rèn)真的服務(wù),與眾多客戶在蓬勃發(fā)展的市場(chǎng)環(huán)境中,互促共生。

Micronaut 是一種基于 jvm 的現(xiàn)代化全??蚣埽糜跇?gòu)建模塊化且易于測(cè)試的微服務(wù)應(yīng)用程序。Micronaut 提供完全的編譯時(shí)、反射無(wú)關(guān)的依賴注入和 AOP。該框架的開(kāi)發(fā)團(tuán)隊(duì)和 Grails 框架的開(kāi)發(fā)團(tuán)隊(duì)是同一個(gè)。Micronaut 框架集成了云技術(shù),服務(wù)發(fā)現(xiàn)、分布式跟蹤、斷路器等微服務(wù)模式也內(nèi)置到了框架中。在本教程中,你將使用不同的語(yǔ)言創(chuàng)建三個(gè)微服務(wù):Java、Kotlin 和 Groovy。你還將了解使用 Micronaut HTTP 客戶端消費(fèi)其他微服務(wù)是多么容易,以及如何創(chuàng)建快速執(zhí)行的功能測(cè)試。

與使用傳統(tǒng) JVM 框架構(gòu)建的應(yīng)用程序不同, Micronaut 提供 100% 的編譯時(shí)、反射無(wú)關(guān)的依賴注入和 AOP。因此,Micronaut 應(yīng)用程序很小,內(nèi)存占用也很低。使用 Micronaut,你可以開(kāi)發(fā)一個(gè)很大的單體應(yīng)用或一個(gè)可以部署到 AWS Lambda 的小函數(shù)??蚣懿粫?huì)限制你。

Micronaut 框架還集成了云技術(shù),服務(wù)發(fā)現(xiàn)、分布式跟蹤、斷路器等微服務(wù)模式也內(nèi)置到了框架中。

Micronaut 在 2018 年 5 月作為開(kāi)源軟件發(fā)布,計(jì)劃在 2018 年底之前發(fā)布 1.0.0 版本?,F(xiàn)在你可以試用 Micronaut,因?yàn)槔锍瘫姹竞桶l(fā)行候選版本已經(jīng)可用。

Micronaut 框架的開(kāi)發(fā)團(tuán)隊(duì)和 Grails 框架的開(kāi)發(fā)團(tuán)隊(duì)是同一個(gè)。Grails 最近迎來(lái)了它的 10 周年紀(jì)念,它繼續(xù)用許多生產(chǎn)力促進(jìn)器幫助開(kāi)發(fā)人員來(lái)編寫 Web 應(yīng)用程序。Grails 3 構(gòu)建在 Spring Boot 之上。你很快就會(huì)發(fā)現(xiàn),對(duì)于使用 Grails 和 Spring Boot 這兩個(gè)框架的開(kāi)發(fā)人員來(lái)說(shuō),Micronaut 有一個(gè)簡(jiǎn)單的學(xué)習(xí)曲線。

教程簡(jiǎn)介

在本系列文章中,我們將使用幾個(gè)微服務(wù)創(chuàng)建一個(gè)應(yīng)用程序:

  • 一個(gè) books 微服務(wù),使用 Groovy 編寫;
  • 一個(gè) inventory 微服務(wù),使用 Kotlin 編寫;
  • 一個(gè) gateway 微服務(wù),使用 Java 編寫。
    你將完成以下工作:
  • 編寫端點(diǎn),使用編譯時(shí)依賴注入;
  • 編寫功能測(cè)試;
  • 配置那些 Micronaut 應(yīng)用程序,注冊(cè)到 Consul;
  • 使用 Micronaut 聲明式 HTTP 客戶端實(shí)現(xiàn)它們之間的通信。
    下圖說(shuō)明了你將要構(gòu)建的應(yīng)用程序:
    Micronaut 教程:如何使用基于 JVM 的框架構(gòu)建微服務(wù)?

微服務(wù)#1 Groovy 微服務(wù)

創(chuàng)建 Micronaut 應(yīng)用的最簡(jiǎn)單方法是使用其命令行接口( Micronaut CLI ),使用 SDKMan 可以輕松安裝。
Micronaut 應(yīng)用程序可以使用 Java、Kotlin 和 Groovy 編寫。首先,讓我們創(chuàng)建一個(gè) Groovy Micronaut 應(yīng)用:

mn create-app example.micronaut.books --lang groovy .

上面的命令創(chuàng)建一個(gè)名為 books 的應(yīng)用,默認(rèn)包為 example.micronaut。

Micronaut 是測(cè)試框架無(wú)關(guān)的。它根據(jù)你使用的語(yǔ)言選擇一個(gè)默認(rèn)測(cè)試框架。在默認(rèn)情況下,Java 使用 JUnit。如果你選擇了 Groovy,在默認(rèn)情況下,將使用 Spock。你可以搭配使用不同的語(yǔ)言和測(cè)試框架。例如,用 Spock 測(cè)試一個(gè) Java Micronaut 應(yīng)用程序。

而且,Micronaut 是構(gòu)建工具無(wú)關(guān)的。你可以使用 Maven 或 Gradle 。默認(rèn)使用 Gradle。

生成的應(yīng)用中包含一個(gè)基于 Netty 的非阻塞 HTTP 服務(wù)器

創(chuàng)建一個(gè)控制器暴露你的第一個(gè) Micronaut 端點(diǎn):

books/src/main/groovy/example/micronaut/BooksController.groovy
package example.micronaut
import groovy.transform.CompileStatic
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
@CompileStatic
@Controller("/api")
class BooksController {
 private final BooksRepository booksRepository
 BooksController(BooksRepository booksRepository) {
 this.booksRepository = booksRepository
 }
 @Get("/books")
 List<Book> list() {
 booksRepository.findAll()
 }
}

在上面的代碼中,有幾個(gè)地方值得一提:

  • 控制器暴露一個(gè) route/api/books 端點(diǎn),可以使用 GET 請(qǐng)求調(diào)用;
  • 注解 @Get 和 @Controller 的值是一個(gè) RFC-6570 URI 模板;
  • 通過(guò)構(gòu)造函數(shù)注入,Micronaut 提供了一個(gè)協(xié)作類:BooksRepository;
  • Micronaut 控制器默認(rèn)消費(fèi)和生成 JSON。
    上述控制器使用了一個(gè)接口和一個(gè) POGO:
books/src/main/groovy/example/micronaut/BooksRepository.groovy
package example.micronaut
interface BooksRepository {
 List<Book> findAll()
}
books/src/main/groovy/example/micronaut/Book.groovy
package example.micronaut
import groovy.transform.CompileStatic
import groovy.transform.TupleConstructor
@CompileStatic
@TupleConstructor
class Book {
 String isbn
 String name
}

Micronaut 在編譯時(shí)把一個(gè)實(shí)現(xiàn)了 BooksRepository 接口的 bean 連接起來(lái)。

對(duì)于這個(gè)應(yīng)用,我們創(chuàng)建了一個(gè)單例,我們是使用 javax.inject.Singleton 注解定義的。

books/src/main/groovy/example/micronaut/BooksRepositoryImpl.groovy
package example.micronaut
import groovy.transform.CompileStatic
import javax.inject.Singleton
@CompileStatic
@Singleton
class BooksRepositoryImpl implements BooksRepository {
 @Override
 List<Book> findAll() {
 [
 new Book("1491950358", "Building Microservices"),
 new Book("1680502395", "Release It!"),
 ]
 }
}

功能測(cè)試的價(jià)值最大,因?yàn)樗鼈儨y(cè)試了整個(gè)應(yīng)用程序。但是,對(duì)于其他框架,很少使用功能測(cè)試和集成測(cè)試。大多數(shù)情況下,因?yàn)樗鼈兩婕暗秸麄€(gè)應(yīng)用程序的啟動(dòng),所以速度很慢。

然而,在 Micronaut 中編寫功能測(cè)試是一件樂(lè)事。因?yàn)樗鼈兒芸?,非??臁?/p>

上述控制器的功能測(cè)試如下:

books/src/test/groovy/example/micronaut/BooksControllerSpec.groovy
package example.micronaut
import io.micronaut.context.ApplicationContext
import io.micronaut.core.type.Argument
import io.micronaut.http.HttpRequest
import io.micronaut.http.client.RxHttpClient
import io.micronaut.runtime.server.EmbeddedServer
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification
class BooksControllerSpec extends Specification {
 @Shared
 @AutoCleanup
 EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer)
 @Shared @AutoCleanup RxHttpClient client = embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL())
 void "test books retrieve"() { 
 when:
 HttpRequest request = HttpRequest.GET('/api/books')
 List<Book> books = client.toBlocking().retrieve(request, Argument.of(List, Book))
 then:
 books books.size() == 2
 }
}

在上述測(cè)試中,有幾個(gè)地方值得一提:

  • 借助 EmbeddedServer 接口,很容易從單元測(cè)試運(yùn)行應(yīng)用程序;
  • 很容易創(chuàng)建一個(gè) HTTP 客戶端 bean 來(lái)消費(fèi)嵌入式服務(wù)器;
  • Micronaut Http 客戶端很容易把 JSON 解析成 Java 對(duì)象。

    微服務(wù)#2 Kotlin 微服務(wù)

    運(yùn)行下面的命令,創(chuàng)建另外一個(gè)名為 inventory 的微服務(wù)。這次,我們使用 Kotlin 語(yǔ)言。

&gt; mn create-app example.micronaut.inventory --lang kotlin
這個(gè)新的微服務(wù)控制著每本書的庫(kù)存。
創(chuàng)建一個(gè) Kotlin數(shù)據(jù)類,封裝屬性域:

inventory/src/main/kotlin/example/micronaut/Book.kt
package example.micronaut
data class Book(val isbn: String, val stock: Int)

創(chuàng)建一個(gè)控制器,返回一本書的庫(kù)存。

inventory/src/main/kotlin/example/micronaut/BookController.kt
package example.micronaut
import io.micronaut.http.HttpResponse import io.micronaut.http.MediaType import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import io.micronaut.http.annotation.Produces
@Controller("/api") 
class BooksController {
 @Produces(MediaType.TEXT_PLAIN) 
 @Get("/inventory/{isbn}") 
 fun inventory(isbn: String): HttpResponse<Int> {
 return when (isbn) { 
 "1491950358" -> HttpResponse.ok(2) 
 "1680502395" -> HttpResponse.ok(3) 
 else -> HttpResponse.notFound()
 }
 }
}

微服務(wù)#3 Java 微服務(wù)

創(chuàng)建一個(gè) Java 網(wǎng)關(guān)應(yīng)用,該應(yīng)用會(huì)消費(fèi) books 和 inventory 這兩個(gè)微服務(wù)。

mn create-app example.micronaut.gateway

如果不指定 lang 標(biāo)識(shí),就會(huì)默認(rèn)選用 Java。

在 gateway 微服務(wù)中,創(chuàng)建一個(gè)聲明式HTTP 客戶端和books 微服務(wù)通信。

首先創(chuàng)建一個(gè)接口:

gateway/src/main/java/example/micronaut/BooksFetcher.java
package example.micronaut;
import io.reactivex.Flowable;
public interface BooksFetcher { 
 Flowable<Book> fetchBooks(); 
}

然后,創(chuàng)建一個(gè)聲明式 HTTP 客戶端,這是一個(gè)使用了 @Client 注解的接口。

gateway/src/main/java/example/micronaut/BooksClient.java
package example.micronaut;
import io.micronaut.context.annotation.Requires; 
import io.micronaut.context.env.Environment; 
import io.micronaut.http.annotation.Get; 
import io.micronaut.http.client.Client; 
import io.reactivex.Flowable;
@Client("books") 
@Requires(notEnv = Environment.TEST) 
public interface BooksClient extends BooksFetcher {
 @Override @Get("/api/books") Flowable<Book> fetchBooks();
}

Micronaut 聲明式 HTTP 客戶端方法將在編譯時(shí)實(shí)現(xiàn),極大地簡(jiǎn)化了 HTTP 客戶端的創(chuàng)建。

此外,Micronaut 支持應(yīng)用程序環(huán)境的概念。在上述代碼清單中,你可以看到,使用 @Requires 注解很容易禁止某些 bean 在特定環(huán)境中加載。

而且,就像你在前面的代碼示例中看到的那樣,非阻塞類型在 Micronaut 中是一等公民。BooksClient::fetchBooks() 方法返回 Flowable<Book>,其中 Book 是一個(gè) Java POJO:

gateway/src/main/java/example/micronaut/Book.java
package example.micronaut;
public class Book {
 private String isbn; 
 private String name; 
 private Integer stock;
 public Book() {}
 public Book(String isbn, String name) { 
 this.isbn = isbn; 
 this.name = name; 
 }
 public String getIsbn() { return isbn; }
 public void setIsbn(String isbn) { this.isbn = isbn; }
 public String getName() { return name; }
 public void setName(String name) { this.name = name; }
 public Integer getStock() { return stock; }
 public void setStock(Integer stock) { this.stock = stock; }
}

創(chuàng)建另外一個(gè)聲明式 HTTP 客戶端,與 inventory 微服務(wù)通信。

首先創(chuàng)建一個(gè)接口:

gateway/src/main/java/example/micronaut/InventoryFetcher.java
package example.micronaut;
import io.reactivex.Maybe;
public interface InventoryFetcher { 
 Maybe<Integer> inventory(String isbn); 
}

然后,一個(gè) HTTP 聲明式客戶端:

gateway/src/main/java/example/micronaut/InventoryClient.java
package example.micronaut;
import io.micronaut.context.annotation.Requires; 
import io.micronaut.context.env.Environment; 
import io.micronaut.http.annotation.Get; 
import io.micronaut.http.client.Client; 
import io.reactivex.Flowable;
import io.reactivex.Maybe; 
import io.reactivex.Single;
@Client("inventory") 
@Requires(notEnv = Environment.TEST)
public interface InventoryClient extends InventoryFetcher {
 @Override 
 @Get("/api/inventory/{isbn}") 
 Maybe<Integer> inventory(String isbn);
}

現(xiàn)在,創(chuàng)建一個(gè)控制器,注入兩個(gè) bean,創(chuàng)建一個(gè)反應(yīng)式應(yīng)答。

gateway/src/main/java/example/micronaut/BooksController.java
package example.micronaut;
import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.reactivex.Flowable;
@Controller("/api") public class BooksController {
 private final BooksFetcher booksFetcher; 
 private final InventoryFetcher inventoryFetcher;
 public BooksController(BooksFetcher booksFetcher, InventoryFetcher inventoryFetcher) {
 this.booksFetcher = booksFetcher;
 this.inventoryFetcher = inventoryFetcher; 
 }
 @Get("/books") Flowable<Book> findAll() { 
 return booksFetcher.fetchBooks()
 .flatMapMaybe(b -> inventoryFetcher.inventory(b.getIsbn())
 .filter(stock -> stock > 0)
 .map(stock -> { 
 b.setStock(stock); 
 return b; 
 })
 );
 }
}

在為控制器創(chuàng)建功能測(cè)試之前,我們需要在測(cè)試環(huán)境中為(BooksFetcher 和 InventoryFetcher)創(chuàng)建 bean 實(shí)現(xiàn)。

創(chuàng)建符合 BooksFetcher 接口的 bean,只適用于測(cè)試環(huán)境;參見(jiàn) @Requires 注解。

gateway/src/test/java/example/micronaut/MockBooksClient.java
package example.micronaut;
import io.micronaut.context.annotation.Requires; 
import io.micronaut.context.env.Environment; 
import io.reactivex.Flowable;
import javax.inject.Singleton;
@Singleton 
@Requires(env = Environment.TEST) 
public class MockBooksClient implements BooksFetcher {
 @Override
 public Flowable<Book> fetchBooks() { 
 return Flowable.just(new Book("1491950358", "Building Microservices"), new Book("1680502395", "Release It!"), new Book("0321601912", "Continuous Delivery:"));
 } 
}

創(chuàng)建符合 InventoryFetcher 接口的 bean,只適用于測(cè)試環(huán)境;

gateway/src/test/java/example/micronaut/MockInventoryClient.java
package example.micronaut;
import io.micronaut.context.annotation.Requires; 
import io.micronaut.context.env.Environment; 
import io.reactivex.Maybe;
import javax.inject.Singleton;
@Singleton 
@Requires(env = Environment.TEST) 
public class MockInventoryClient implements InventoryFetcher {
 @Override 
 public Maybe<Integer> inventory(String isbn) { 
 if (isbn.equals("1491950358")) { 
 return Maybe.just(2); 
 } 
 if (isbn.equals("1680502395")) { 
 return Maybe.just(0); 
 } 
 return Maybe.empty();
 } 
}

創(chuàng)建功能測(cè)試。在 Groovy 微服務(wù)中,我們編寫了一個(gè) Spock 測(cè)試,這次,我們編寫 JUnit 測(cè)試。

gateway/src/test/java/example/micronaut/BooksControllerTest.java
package example.micronaut;
import io.micronaut.context.ApplicationContext;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.HttpClient;
import io.micronaut.runtime.server.EmbeddedServer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.List;
public class BooksControllerTest {
 private static EmbeddedServer server; 
 private static HttpClient client;
 @BeforeClass 
 public static void setupServer() {
 server = ApplicationContext.run(EmbeddedServer.class); 
 client = server .getApplicationContext() .createBean(HttpClient.class, server.getURL());
 }
 @AfterClass 
 public static void stopServer() {
 if (server != null) { 
 server.stop();
 }
 if (client != null) { 
 client.stop();
 }
 }
 @Test 
 public void retrieveBooks() { 
 HttpRequest request = HttpRequest.GET("/api/books"); 
 List<Book> books = client.toBlocking().retrieve(request, Argument.of(List.class, Book.class)); 
 assertNotNull(books); 
 assertEquals(1, books.size());
 } 
}

服務(wù)發(fā)現(xiàn)

我們將配置我們的 Micronaut 微服務(wù),注冊(cè)到 Consul 服務(wù)發(fā)現(xiàn)。

Consul 是一個(gè)分布式服務(wù)網(wǎng)格,用于跨任何運(yùn)行時(shí)平臺(tái)和公有或私有云連接、防護(hù)和配置服務(wù)。

Micronaut 與 Consul 的集成很簡(jiǎn)單。

首先向 books、inventory 和 gateway 三個(gè)微服務(wù)中的每一個(gè)添加服務(wù)發(fā)現(xiàn)客戶端依賴項(xiàng):

gateway/build.gradle
runtime "io.micronaut:discovery-client"
books/build.gradle
runtime "io.micronaut:discovery-client"
inventory/build.gradle
runtime "io.micronaut:discovery-client"

我們需要對(duì)每個(gè)應(yīng)用的配置做一些修改,以便應(yīng)用啟動(dòng)時(shí)注冊(cè)到 Consul。

gateway/src/main/resources/application.yml
micronaut:
 application:
 name: gateway 
 server:
 port: 8080
consul:
 client:
 registration: 
 enabled: true
 defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
books/src/main/resources/application.yml
micronaut:
 application:
 name: books
 server:
 port: 8082
consul:
 client:
 registration: 
 enabled: true
 defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
inventory/src/main/resources/application.yml
micronaut:
 application:
 name: inventory
 server:
 port: 8081
consul:
 client:
 registration: 
 enabled: true
 defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"

每個(gè)服務(wù)在 Consul 中注冊(cè)時(shí)都使用屬性 microaut.application .name 作為服務(wù) id。這就是為什么我們?cè)谇懊娴?@Client 注解中使用那些明確的名稱。

前面的代碼清單展示了 Micronaut 的另一個(gè)特性,配置文件中有帶默認(rèn)值的環(huán)境變量插值,如下所示:

defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"

另外,在 Micronaut 中可以有特定于環(huán)境的配置文件。我們將在每個(gè)環(huán)境中創(chuàng)建一個(gè)名為 application-test.yml 的文件,用于測(cè)試階段的 Consul 注冊(cè)。


gateway/src/test/resources/application-test.yml
consul:
 client:
 registration: enabled: false
books/src/test/resources/application-test.yml
consul:
 client:
 registration: enabled: false
inventory/src/test/resources/application-test.yml
consul:
 client:
 registration: enabled: false

**運(yùn)行應(yīng)用
開(kāi)始使用 Consul 的最簡(jiǎn)單方式是通過(guò) Docker?,F(xiàn)在,運(yùn)行一個(gè) Docker 實(shí)例。

docker run -p 8500:8500 consul

使用 Gradle 創(chuàng)建一個(gè)多項(xiàng)目構(gòu)建。在根目錄下創(chuàng)建一個(gè)settings.gradle 文件。

settings.gradle
include 'books' 
include 'inventory' 
include 'gateway'

現(xiàn)在,你可以并行運(yùn)行每個(gè)應(yīng)用了。Gradle 為此提供了一個(gè)方便的標(biāo)識(shí)(-parallel):

./gradlew -parallel run

每個(gè)微服務(wù)都在配置好的端口上啟動(dòng):8080、8081 和 8082。

Consul 提供了一個(gè) HTML UI。在瀏覽器中打開(kāi) http://localhost:8500/ui,你會(huì)看到:
Micronaut 教程:如何使用基于 JVM 的框架構(gòu)建微服務(wù)?

每個(gè) Micronaut 微服務(wù)都已注冊(cè)到 Consul。

你可以使用下面的 curl 命令調(diào)用網(wǎng)關(guān)微服務(wù):


$ curl http://localhost:8080/api/books [{"isbn":"1680502395","name":"Release It!","stock":3}, {"isbn":"1491950358","name":"Building Microservices","stock":2}]

恭喜你已經(jīng)創(chuàng)建好了第一個(gè) Micronaut 微服務(wù)網(wǎng)絡(luò)!

小結(jié)

在本教程中,你用不同的語(yǔ)言創(chuàng)建了三個(gè)微服務(wù):Java、Kotlin 和 Groovy。你還了解了使用 Micronaut HTTP 客戶端消費(fèi)其他微服務(wù)是多么容易,以及如何創(chuàng)建快速執(zhí)行的功能測(cè)試。

此外,你創(chuàng)建的一切都可以利用完全反射無(wú)關(guān)的依賴注入和 AOP。

分享標(biāo)題:Micronaut教程:如何使用基于JVM的框架構(gòu)建微服務(wù)?
網(wǎng)頁(yè)路徑:http://www.muchs.cn/article38/ishesp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供移動(dòng)網(wǎng)站建設(shè)、域名注冊(cè)、做網(wǎng)站、品牌網(wǎng)站制作、自適應(yīng)網(wǎng)站、外貿(mào)網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

搜索引擎優(yōu)化