乐趣区

关于spring:通过Spring-Boot构建一个购物车微服务

在 Spring MVC 中,您能够创立 REST 应用程序。

您创立一个应用 @RestController 正文的控制器类。

而后定义应用 @RequestMapping 正文的处理程序办法。

您还能够应用 @PathVariable 正文的 URI 模板参数。此处显示的代码示例公开了 REST 端点 / users / {user}。{user}的代码是用于自定义 REST 申请的门路变量。Spring Boot 提供了一个 Jackson ObjectMapper 的主动配置,用于 JSON 无效负载和 POJO 对象之间的主动编组。

Spring 在构建 REST 资源时,通常应用 @RestController 和 @RequestMapping 来定义。而 JavaEE 定义的时候,通常应用 JAX-RS 实现 @Path。当然,在 Spring 中也能够应用 JAX-RS。

作为开发的另一种抉择,您能够应用 JAX-RS。默认的 Spring Boot JAX-RS 实现是 Jersey,它是 Glassfish Jax-RS 实现。您能够应用 spring-boot-starter-jersey Spring Boot 启动器。Spring Boot 提供 Jersey servlet 和 Jackson 数据绑定的主动配置。您能够应用规范的 JAX-RS 正文构建 REST 端点,例如 @ javax.ws.rs.Path,@ javax.ws.rs.Produces 和 javax.ws.rs.GET。此代码示例演示如何应用 JAX-RS 在 Spring 中构建 REST 资源。

REST 资源类须要在 Jersey 上下文中注册。这里显示了一个例子。

Spring Boot 包含对嵌入式 Tomcat,Jetty 和 Undertow 服务器的反对。默认状况下,Spring Boot 启动程序(特地是 spring-boot-starter-web)应用 Tomcat 作为嵌入式容器。要应用备用服务器,请排除 Tomcat 依赖项并蕴含所需的依赖项。这里的代码片段显示了如何排除 Tomcat 并蕴含 Undertow 服务器所需的依赖关系。

  • Spring Framework 为应用 SQL 数据库提供了宽泛的反对。您能够应用 JdbcTemplate 间接进行 JDBC 拜访。您还能够将 JPA 对象关系映射与 Hibernate 一起应用。Spring Data 为反对 SQL,NoSQL,MapReduce 框架和基于云的数据服务的数据拜访提供了基于 Spring 的编程模型。Spring Boot 提供以下启动器:spring-boot-starter-jdbc 和 spring-boot-starter-data-jpa。Spring Boot 的一个很好的性能是内存数据库的主动配置,非常适合测试。此外,Spring Boot 还提供具备内部配置属性的数据源的主动配置。

Spring Framework 还提供实体类扫描,以及 Spring Data Repository 类的主动注册。Spring JPA 存储库是封装数据拜访的接口。JPA 查问是从办法名称主动创立的。这里显示了一个例子。

Spring Boot 配置能够内部化,以便雷同的利用程序代码能够在不同的环境中工作。您能够应用属性文件,YAML 文件,环境变量和命令行参数来内部化配置。能够应用 @Value 正文将属性值间接注入 bean,通过 Spring 的 Environment 形象拜访,或通过 @ConfigurationProperties 绑定到结构化对象。

Spring Boot 应用一个有序的序列来指定属性,以便容许正当地笼罩值。程序如下:

@TestPropertySource

命令行参数

Java 零碎属性

OS 环境变量

打包 JAR 之外的特定于配置文件的应用程序属性

打包的 JAR 中的特定于配置文件的应用程序属性

默认属性

应用 @Value(“$ {property}”)正文来注入配置属性可能很麻烦。Spring Boot 提供了另一种抉择。您能够定义强类型 bean 来治理和验证应用程序的配置。

上面的代码示例定义了以下属性:

foo.enabled,默认为 false

foo.remote-address,具备能够从 String 强制转换的类型

foo.security.username,具备嵌套安全性

foo.security.roles,带有 String 汇合

您能够像应用任何其余 bean 一样注入此配置。

Spring Profiles 提供了一种隔离应用程序配置局部的办法,并使每个局部仅在某些环境中可用 – 例如,dev,QA 或 production。

任何 @Component 或 @Configuration 都能够用 @Profile 标记,以限度何时加载。

特定于配置文件的属性文件名为 application- {profile} .properties。

当配置文件处于活动状态时,您能够笼罩 application.properties 中的默认属性。

  • 您能够通过以下任何形式指定流动配置文件:应用 -Dspring.profiles.active = dev 作为命令行上的零碎属性 作为应用导出 SPRING_PROFILES_ACTIVE = dev 的环境变量 在应用 spring.profiles.active = dev 的应用程序属性中 在应用 @ActiveProfiles(“test”)的测试用例中

Spring Cloud 是一个用于开发云原生应用程序的框架。Spring Cloud 提供了通用设计模式的实现,以反对云本机应用程序的开发。Spring Cloud 提供的解决方案:

  • 集中配置管理
  • 服务注册和发现
  • 负载平衡
  • 断路器
  • 异步通信
  • 分布式跟踪

Spring Cloud 还提供第三方工具和库的集成和形象:

  • Netflix OSS Eureka 服务注册表
  • HashiCorp 服务注销
  • Netflix OSS Hystrix 断路器和隔板
  • Netflix OSS 功能区客户端负载均衡器
  • Apache Kafka 和 RabbitMQ 音讯代理
  • Zipkin 分布式追踪

Spring Cloud Kubernetes 提供 Spring Cloud 与 Kubernetes 和 OpenShift 的集成。它由 Red Hat Fabric8.io 团队发动,当初由 Spring Cloud Incubator 托管。

性能包含以下内容:

  • 带有 ConfigMaps 和 secret 的 Spring Boot 配置
  • 当在 ConfigMap 中检测到更改时,PropertySource 从新加载以触发应用程序从新加载
  • pod 运行状况指示器,用于将特定于 pod 的运行状况数据增加到 Spring Actuator 运行状况端点
  • 在 Kubernetes 上运行时,Kubernetes 配置文件主动配置
  • Kubernetes 的功能区发现
  • Archaius-a Netflix OSS 配置管理库 -ConfigMap 属性源
  • 透明度 – 当应用程序在 Kubernetes / OpenShift 之外运行时,Spring Cloud Kubernetes 不会中断

在 Red Hat Fuse Integration Services 2.x 版中,Spring Boot 是在 OpenShift 上开发 Camel 应用程序的首选框架。启动器模块是 camel-spring-boot-starter。CamelContext 的主动配置在 Spring 应用程序上下文中注册。应用 @Component 正文的 Camel 路由会自动检测并注入 CamelContext。

二:试验展示:构建购物车

在本试验中,您增加了为 Coolstore 应用程序的购物车微服务公开 REST API 的性能。实验室从上一个实验室的解决方案代码开始,包含您应用的其余文件。

为购物车微服务实现和公开 REST API

查看并运行 REST API 的单元测试

利用架构

购物车微服务由一个 Maven 我的项目组成,该我的项目外部由许多服务对象组成:

PriceCalculationService 蕴含用于计算购物车的运费和总价值的逻辑。

CatalogService 负责调用目录服务以获取产品数据。

ShoppingCartService 负责管理购物车。

CartEndpoint 蕴含用于拜访购物车微服务的 REST API。

在本试验中,您将增加 REST API 的实现以拜访 CartEndpoint 类中的购物车微服务。

启动 Red Hat Developer Studio。抉择文件→导入。在“导入”对话框中,抉择“Maven”→“现有 Maven 我的项目”,而后单击“下一步”。单击“浏览”并导航到〜/ appmod_springboot_experienced / lab-02。这是您解压缩本试验的代码的目录。确保为我的项目选中了 /pom.xml 框,而后单击 Finish。导入后,验证您是否看到该我的项目。

查看购物车服务项目的 pom.xml 文件,并留神以下事项:在 dependencyManagement 局部中,导入 spring-boot-dependencies 物料清单(BOM)POM。此 POM 蕴含特定 Spring Boot 版本反对的所有依赖项的策动列表。实际上,这意味着您不用手动跟踪在构建配置中增加的依赖项的版本,因为 Spring Boot 正在为您治理。降级 Spring Boot 自身时,依赖关系也会以统一的形式降级。在 Red Hat OpenShift Application Runtimes 环境中认证的 Spring Boot 版本是 1.5.10.RELEASE。spring-boot-maven 插件用于构建可执行的 JAR 文件(fat JAR)。插件的从新打包指标创立了一个可主动执行的 JAR(或 WAR)文件。

应用 Spring Boot 开发一个购物车微服务 应用 Spring Boot,能够应用不同的技术来构建 REST API。您能够应用 Spring MVC 框架或实现 JAX-RS 标准的框架。对于购物车服务,须要以下 REST 端点:GET / cart / {cartId}按 ID 获取购物车。POST / cart / {cartId} / {itemId} / {quantity}将商品增加到购物车。DELETE / cart / {cartId} / {itemId} / {quantity}从购物车中删除商品。POST / cart / checkout / {cartId}查看购物车。

咱们查看源码,进行剖析:

package com.redhat.coolstore.cart.rest;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import com.redhat.coolstore.cart.service.ShoppingCartService;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import com.redhat.coolstore.cart.model.ShoppingCart;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.DeleteMapping;

@RestController

@RequestMapping(“/cart”)

//@RequestMapping 批注标识资源类或类办法为其申请的 URI 门路。购物车服务的 URI 门路是 / cart。@RestController 正文将此类标识为 REST 资源 //

public class CartEndpoint {

private static final Logger LOG = LoggerFactory.getLogger(CartEndpoint.class);

// 应用 SLF4J API 配置一个 logger//

    @Autowired

    private ShoppingCartService shoppingCartService;

// 应用 Spring @Autowired 正文注入 ShoppingCartService//

    @GetMapping(“/{cartId}”)

    public ShoppingCart getCart(@PathVariable String cartId) {

        return shoppingCartService.getShoppingCart(cartId);

    }

//@GetMapping 示意带正文的办法响应 HTTP GET 申请。URI 局部附加到类级别定义的根本门路。{cartId}示意模板参数名称。@PathVariable 将 URI 模板参数的值绑定到办法变量。此代码将调用委托给 ShoppingCartService.getShoppingCart 办法。//

    @PostMapping(“/{cartId}/{itemId}/{quantity}”)

    public ShoppingCart add(@PathVariable String cartId,

                            @PathVariable String itemId,

                            @PathVariable int quantity) {

            return shoppingCartService.addToCart(cartId, itemId, quantity);

    }

// 此办法实现 REST POST / cart / {cartId} / {itemId} / {quantity}端点。//

    @DeleteMapping(“/{cartId}/{itemId}/{quantity}”)

    public ShoppingCart delete(@PathVariable String cartId,

                                    @PathVariable String itemId,

                                    @PathVariable int quantity) {

        return shoppingCartService.removeFromCart(cartId, itemId, quantity);

    }

// 此办法实现 DELETE / cart / {cartId} / {itemId} / {quantity}端点。//

    @PostMapping(“/checkout/{cartId}”)

    public ShoppingCart checkout(@PathVariable String cartId) {

        ShoppingCart cart = shoppingCartService.checkoutShoppingCart(cartId);

        LOG.info(“ShoppingCart ” + cart + ” checked out”);

        return cart;

    }

}

// 此办法实现 REST POST / cart / checkout / {cartId}端点。目前,只需记录购物车已签出的事实就足够了。//

查看并运行端到端集成测试 此时,您已筹备好所有局部。在本节中,您将查看并运行购物车服务的端到端测试(或集成测试)。Spring Boot 容许集成测试,而无需理论部署应用程序或连贯到其余基础架构。Spring Boot 应用程序作为测试自身的一部分进行自举。所须要的只是依赖于 spring-boot-starter-test 启动器。您能够应用不同的技术在集成测试中测试 REST 端点。REST Assured 是一种晦涩而优雅的 Java DSL,用于简化基于 REST 的服务的测试,可用于验证和验证这些服务的响应。Rest Assured 使得验证 JSON 或 XML 无效负载变得特地容易。要模仿近程目录服务,您能够应用 WireMock 框架,就像在 CatalogService 服务的测试中一样。Spring Boot 应用程序 – 更具体地说,是 CatalogService 实现 – 冀望将 catalog.service.url 零碎属性设置为近程目录服务的 URL。将此属性注入测试的一种办法是利用 Spring 配置文件和 Spring Boot 对特定于配置文件的属性的反对。在我的项目的 src / test / resources 文件夹中,查看名为 application-test.properties 的文件:

无需为此属性设置值,因为理论的 URL(特地是 WireMock 服务器绑定的端口)在执行测试自身之前是未知的。您将理论 URL 注入测试代码。Spring Boot 从类门路根目录中的 application.properties 和 application- {profile} .properties 文件加载属性,并将它们增加到 Spring 环境中。在这种状况下,仅在测试配置文件处于活动状态时才加载 application-t

est.properties。

抉择我的项目的 src / test / java 文件夹。查看 com.redhat.coolstore.cart.rest 包中的 CartEndpointTest 类。

此批注激活测试配置文件,因而 application-test.properties 文件将加载到 Spring 上下文中。

咱们查看测试的源码:

package com.redhat.coolstore.cart.rest;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;

import static com.github.tomakehurst.wiremock.client.WireMock.get;

import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;

import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;

import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;

import static io.restassured.RestAssured.given;

import static org.hamcrest.Matchers.equalTo;

import static org.hamcrest.Matchers.hasItems;

import static org.hamcrest.Matchers.hasSize;

import java.io.InputStream;

import java.nio.charset.Charset;

import org.apache.commons.io.IOUtils;

import org.junit.Before;

import org.junit.Rule;

import org.junit.Test;

import org.junit.runner.RunWith;

//SpringRunner 类提供了 JUnit 测试框架和 Spring 框架之间的集成。当应用 SpringRunner,Spring 应用程序上下文时 – 在 Spring Boot 的状况下,这是 Spring Boot 应用程序自身 – 在测试和启用 Spring 组件的依赖注入之前的 bootstraps。//

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.context.embedded.LocalServerPort;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.annotation.DirtiesContext;

import org.springframework.test.context.ActiveProfiles;

import org.springframework.test.context.junit4.SpringRunner;

import org.springframework.test.util.ReflectionTestUtils;

import com.github.tomakehurst.wiremock.junit.WireMockRule;

import com.redhat.coolstore.cart.service.CatalogService;

import io.restassured.RestAssured;

import io.restassured.http.ContentType;

@ActiveProfiles(“test”)

@RunWith(SpringRunner.class)

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

//@SpringBootTest 正文在惯例 Spring 测试框架上提供了一些特定于 Spring Boot 的加强性能。特地是,SpringBootTest.WebEnvironment.RANDOM_PORT 环境加载了一个嵌入式 WebApplicationContext 并提供了一个真正的 servlet 环境。

嵌入式 servlet 容器(在本例中为 Tomcat)在随机端口上启动和监听。

//

public class CartEndpointTest {

    @LocalServerPort

    private int port;

    @Rule

    public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort());

    @Autowired

    private CatalogService catalogService;

    @Before

    public void beforeTest() throws Exception {

        RestAssured.baseURI = String.format(“http://localhost:%d/cart”, port);

        ReflectionTestUtils.setField(catalogService, null, “catalogServiceUrl”, “http://localhost:” + wireMockRule.port(), null);

        initWireMockServer();

    }

    @Test

    public void retrieveCartById() throws Exception {

        given().get(“/{cartId}”, “123456”)

            .then()

            .assertThat()

            .statusCode(200)

            .contentType(ContentType.JSON)

            .body(“id”, equalTo(“123456”))

            .body(“cartItemTotal”, equalTo(0.0f))

            .body(“shoppingCartItemList”, hasSize(0));

    }

    @Test

    @DirtiesContext

// 为了可能测试调用近程目录服务的 REST 端点,能够应用 WireMock 模仿目录服务。因为 WireMock 服务器绑定到随机端口(对于每个测试方法可能都是不同的端口),因而必须应用 ReflectionTestUtils 将理论的 WireMock URL 注入 CatalogService 实例。

应用 WireMock 时,会为每个测试方法实例化 WireMock 服务器的新实例,并绑定到不同的端口。因而,还必须为应用 WireMock 服务器的测试方法从新创立 Spring 上下文。这能够通过应用 Spring @DirtiesContext 正文来正文这些测试方法来实现。

//

    public void addItemToCart() throws Exception {

        given().post(“/{cartId}/{itemId}/{quantity}”, “234567”, “111111”, new Integer(1))

            .then()

            .assertThat()

            .statusCode(200)

            .contentType(ContentType.JSON)

            .body(“id”, equalTo(“234567”))

            .body(“cartItemTotal”, equalTo(new Float(100.0)))

            .body(“shoppingCartItemList”, hasSize(1))

            .body(“shoppingCartItemList.product.itemId”, hasItems(“111111”))

            .body(“shoppingCartItemList.price”, hasItems(new Float(100.0)))

            .body(“shoppingCartItemList.quantity”, hasItems(new Integer(1)));

    }

    @Test

    @DirtiesContext

    public void addExistingItemToCart() throws Exception {

        given().post(“/{cartId}/{itemId}/{quantity}”, “345678”, “111111”, new Integer(1));

        given().post(“/{cartId}/{itemId}/{quantity}”, “345678”, “111111”, new Integer(1))

            .then()

            .assertThat()

            .statusCode(200)

            .contentType(ContentType.JSON)

            .body(“id”, equalTo(“345678”))

            .body(“cartItemTotal”, equalTo(new Float(200.0)))

            .body(“shoppingCartItemList”, hasSize(1))

            .body(“shoppingCartItemList.product.itemId”, hasItems(“111111”))

            .body(“shoppingCartItemList.price”, hasItems(new Float(100.0)))

            .body(“shoppingCartItemList.quantity”, hasItems(new Integer(2)));

    }

    @Test

    @DirtiesContext

    public void addItemToCartWhenCatalogServiceThrowsError() throws Exception {

        given().post(“/{cartId}/{itemId}/{quantity}”, “234567”, “error”, new Integer(1))

            .then()

            .assertThat()

            .statusCode(500);

    }

    @Test

    @DirtiesContext

    public void removeAllInstancesOfItemFromCart() throws Exception {

        given().post(“/{cartId}/{itemId}/{quantity}”, “456789”, “111111”, new Integer(2));

        given().delete(“/{cartId}/{itemId}/{quantity}”, “456789”, “111111”, new Integer(2))

            .then()

            .assertThat()

            .statusCode(200)

            .contentType(ContentType.JSON)

            .body(“id”, equalTo(“456789”))

            .body(“cartItemTotal”, equalTo(new Float(0.0)))

            .body(“shoppingCartItemList”, hasSize(0));

    }

    @Test

    @DirtiesContext

    public void removeSomeInstancesOfItemFromCart() throws Exception {

        given().post(“/{cartId}/{itemId}/{quantity}”, “567890”, “111111”, new Integer(3));

        given().delete(“/{cartId}/{itemId}/{quantity}”, “567890”, “111111”, new Integer(1))

            .then()

            .assertThat()

            .statusCode(200)

            .contentType(ContentType.JSON)

            .body(“id”, equalTo(“567890”))

            .body(“cartItemTotal”, equalTo(new Float(200.0)))

            .body(“shoppingCartItemList”, hasSize(1))

            .body(“shoppingCartItemList.quantity”, hasItems(new Integer(2)));

    }

    @Test

    @DirtiesContext

    public void checkoutCart() throws Exception {

        given().post(“/{cartId}/{itemId}/{quantity}”, “678901”, “111111”, new Integer(3));

        given().post(“/checkout/{cartId}”, “678901”)

            .then()

            .assertThat()

            .statusCode(200)

            .contentType(ContentType.JSON)

            .body(“id”, equalTo(“678901”))

            .body(“cartItemTotal”, equalTo(new Float(0.0)))

            .body(“shoppingCartItemList”, hasSize(0));

    }

    private void initWireMockServer() throws Exception {

        InputStream isresp = Thread.currentThread().getContextClassLoader().getResourceAsStream(“catalog-response.json”);

        stubFor(get(urlEqualTo(“/product/111111”)).willReturn(

                aResponse().withStatus(200).withHeader(“Content-type”, “application/json”).withBody(IOUtils.toString(isresp, Charset.defaultCharset()))));

        stubFor(get(urlEqualTo(“/product/error”)).willReturn(

                aResponse().withStatus(500)));

    }

}

应用 Red Hat Developer Studio 中的 JUnit 测试运行器运行测试。

或者,在命令行中应用 Maven:

开始测试:

测试过程中的打印:

14 项测试胜利:

https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
https://github.com/aolu308189…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…
http://www.lukou.com/userfeed…

退出移动版