云原生-Quarkus配置参考指南

Quarkus应用程序和Quarkus本身(核心和扩展)都通过相同的机制进行配置,该机制利用SmallRye Config API和 MicroProfile Config规范的实现。此外,Quarkus本身提供了一些附加功能。

1.配置源

默认情况下,Quarkus从多个来源(优先级递减)中读取配置属性:

  1. 系统属性
  2. 环境变量
  3. 名为文件的文件.env放置在当前工作目录中
  4. application.properties文件放在$PWD/config/目录中
  5. 应用程序配置文件,即src/main/resources/application.properties

1.1.系统属性

  • 运行jar: java -Dquarkus.datasource.password=youshallnotpass -jar target/quarkus-app/quarkus-run.jar
  • native执行文件: ./target/myapp-runner -Dquarkus.datasource.password=youshallnotpass

1.2.环境变量

    • 运行jar: export QUARKUS_DATASOURCE_PASSWORD=youshallnotpass ; java -jar target/quarkus-app/quarkus-run.jar
    • native可执行文件: export QUARKUS_DATASOURCE_PASSWORD=youshallnotpass ; ./target/myapp-runner

1.3.文件名为.env放置在当前工作目录中

示例.env文件

QUARKUS_DATASOURCE_PASSWORD=youshallnotpass 
QUARKUS_DATASOURCE_PASSWORD使用与环境变量相同的规则来转换名称。

对于开发人员模式,此文件可以放在项目的根目录中,但建议不要将其检入版本控制。

没有定义的配置文件的环境变量.env文件将覆盖其所有相关的配置文件中application.properties,例如,%test.application.value被覆盖APPLICATION_VALUE.env文件。

1.4.application.properties文件放置在$PWD/config/

application.properties文件放置在名为config的目录中,该目录位于应用程序运行的目录中,该文件中定义的所有运行时属性都将覆盖默认配置。此外,添加到该文件中的任何不属于原始application.properties文件的 运行时属性将被考虑在内。对于运行程序jar和本机可执行文件,其工作方式相同。

config/application.properties功能在开发模式下也可用。要使用它,config/application.properties需要放置在构建工具的输出目录中(target对于Maven和build/classes/java/mainGradle)。但是请记住,从构建工具进行的任何清理操作(例如mvn clean或)gradle clean也会删除该config目录。

1.5.应用程序配置文件

这是位于中的主要应用程序配置文件src/main/resources/application.properties

示例application.properties文件

greeting.message=hello 
quarkus.http.port=9090 
  • 这是用户定义的配置属性。
  • 这是用户定义的配置属性。
  • Quarkus支持在文件中使用属性表达式application.properties

2.将配置文件嵌入到依赖项中

可以通过向META-INF/microprofile-config.properties配置文件中添加一个配置文件来将配置文件嵌入其中(这是MicroProfile Config的标准功能)。

当将此依赖项添加到应用程序时,其配置属性将被合并。

可以覆盖优先于它的属性来自它的属性application.properties

3.注入配置属性

Quarkus使用MicroProfile Config注释在应用程序中注入配置属性。

@ConfigProperty(name = "greeting.message") 
String message;

可以使用@Inject @ConfigProperty@ConfigProperty。对于使用@Inject注释的成员,注释不是必需的@ConfigProperty。此行为不同于MicroProfile Config。

如果应用程序尝试注入未设置的配置属性,则会引发错误,从而使您可以快速了解配置何时完成。

更多@ConfigProperty例子

@ConfigProperty(name = "greeting.message") 
String message;

@ConfigProperty(name = "greeting.suffix", defaultValue="!") 
String suffix;

@ConfigProperty(name = "greeting.name")
Optional<String> name; 

如果您不提供此属性的值,则应用程序启动将失败javax.enterprise.inject.spi.DeploymentException: No config value of type [class java.lang.String] exists for: greeting.message

如果配置未提供的值,则会注入默认值greeting.suffix

此属性是可选的-Optional如果配置未提供的值,则会注入一个空值greeting.name

4.以编程方式访问配置

可以通过编程方式访问配置。实现动态查找或从既不是CDI bean也不是JAX-RS资源的类中检索配置的值可能很方便。

可以使用以下方式以编程方式访问配置org.eclipse.microprofile.config.ConfigProvider.getConfig()

String databaseName = ConfigProvider.getConfig().getValue("database.name", String.class);
Optional<String> maybeDatabaseName = ConfigProvider.getConfig().getOptionalValue("database.name", String.class);

5.使用@ConfigProperties

作为以上一示例中所示的方式注入多个相关配置值的替代方法,用户还可以使用@io.quarkus.arc.config.ConfigProperties注释将这些属性组合在一起。

对于上面的Greeting属性,GreetingConfiguration可以这样创建一个类:

package org.acme.config;

import io.quarkus.arc.config.ConfigProperties;
import java.util.Optional;

@ConfigProperties(prefix = "greeting") 
public class GreetingConfiguration {

    private String message;
    private String suffix = "!"; 
    private Optional<String> name;

    public String getMessage() {
        return message;
    }

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

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }

    public Optional<String> getName() {
        return name;
    }

    public void setName(Optional<String> name) {
        this.name = name;
    }
}

然后可以GreetingResource使用@Inject类似的CDI注释将此类注入到,如下所示:

@Inject
GreetingConfiguration greetingConfiguration;

Quarkus提供的另一种替代样式是GreetingConfiguration像这样创建接口:

package org.acme.config;

import io.quarkus.arc.config.ConfigProperties;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import java.util.Optional;

@ConfigProperties(prefix = "greeting")
public interface GreetingConfiguration {

    @ConfigProperty(name = "message") 
    String message();

    @ConfigProperty(defaultValue = "!")
    String getSuffix(); 

    Optional<String> getName(); 
}

@ConfigProperties类或接口上使用时,如果未提供其字段之一的值,则应用程序启动将失败,并且将javax.enterprise.inject.spi.DeploymentException显示指示缺少值的信息。这不适用于Optional字段和具有默认值的字段。

5.1.有关@ConfigProperties的其他说明

当使用带有类注释的常规类时,@ConfigProperties不一定必须声明getter和setter。具有简单的公共非最终字段也是有效的。

此外,配置类支持嵌套对象配置。假设需要有一个额外的问候语配置层,名称content将包含一些字段。可以这样实现:

@ConfigProperties(prefix = "greeting")
public class GreetingConfiguration {

    public String message;
    public String suffix = "!";
    public Optional<String> name;
    public ContentConfig content; 

    public static class ContentConfig {
        public Integer prizeAmount;
        public List<String> recipients;
    }
}

字段名称(不是类名称)将确定绑定到对象的属性的名称。

设置属性将以正常方式进行,例如,application.properties可能具有:

greeting.message = hello
greeting.name = quarkus
greeting.content.prize-amount=10
greeting.content.recipients=Jane,John

此外,@ConfigProperties可以使用Bean Validation批注来批注使用批注的类,类似于以下示例:

@ConfigProperties(prefix = "greeting")
public class GreetingConfiguration {

    @Size(min = 20)
    public String message;
    public String suffix = "!";

}

为了使验证生效,quarkus-hibernate-validator需要提供扩展名

如果使用给定的配置验证失败,则应用程序将无法启动,并在日志中指示相应的验证错误。

如果接口带有注释@ConfigProperties,则允许该接口扩展其他接口,并且使用整个接口层次结构中的方法来绑定属性。

5.2.使用具有不同前缀的相同ConfigProperty

Quarkus还支持使用注解@ConfigProperties为每个注入点使用带有不同前缀的同一对象io.quarkus.arc.config.@ConfigPrefix。举例来说,前缀和前缀GreetingConfiguration都需要使用以上代码。在这种情况下,代码如下所示:greetingother

GreetingConfiguration.java

@ConfigProperties(prefix = "greeting")
public class GreetingConfiguration {

    @Size(min = 20)
    public String message;
    public String suffix = "!";

}

SomeBean.java

@ApplicationScoped
public class SomeBean {

    @Inject 
    GreetingConfiguration greetingConfiguration;

    @ConfigPrefix("other") 
    GreetingConfiguration otherConfiguration;

}

5.3.使用对象列表

在某些情况下,可能有必要支持利用对象列表的复杂配置结构,如以下示例所示:

ComplexConfiguration.java

@ConfigProperties(prefix = "complex")
public class ComplexConfiguration {
    public String name;
    public String user;
    public String password;
    public List<Nested> inputs;
    public List<Nested> outputs;

    public static class Nested {
        public String user;
        public String password;
    }
}

仅当将YAML配置与quarkus-config-yaml扩展一起使用时,才对此类用例提供支持。相应的示例YAML配置可以是:

application.yaml

complex:
  name: defaultName
  user: defaultUser
  password: defaultPassword
  inputs:
    - user: user
      password: secret
    - user: otheruser
      password: secret2
  outputs:
    - user: someuser
      password: asecret
    - user: someotheruser
      password: anothersecret
  • 这种配置的局限性在于用作列表的通用类型的类型必须是类而不是接口
  • 6.配置配置文件

    Quarkus支持配置配置文件的概念。这些允许您在同一文件中具有多个配置,并通过配置文件名称在它们之间进行选择。

    语法为%{profile}.config.key=value。例如,如果我有以下内容:

    quarkus.http.port=9090
    %dev.quarkus.http.port=8181

    除非dev配置文件处于活动状态,否则Quarkus HTTP端口将为9090 ,在这种情况下,它将为8181。

    要在.env文件中使用配置文件,可以遵循一种_{PROFILE}_CONFIG_KEY=value模式。.env文件中上述示例的等效内容为:

    QUARKUS_HTTP_PORT=9090
    _DEV_QUARKUS_HTTP_PORT=8181

    默认情况下,Quarkus具有三个配置文件,尽管可以随意使用。默认配置文件为:

    • dev-在开发模式下(即quarkus:dev)激活
    • 测试-运行测试时激活
    • prod-未在开发或测试模式下运行时的默认配置文件

    有两种方法可以通过quarkus.profile系统属性或QUARKUS_PROFILE 环境变量来设置定制概要文件。如果两者都设置,则系统属性优先。请注意,不必在任何地方定义这些概要文件的名称,只需创建具有概要文件名称的config属性,然后将当前概要文件设置为该名称即可。例如,如果我想要staging使用其他HTTP端口的配置文件,则可以将以下内容添加到application.properties

    quarkus.http.port=9090
    %staging.quarkus.http.port=9999

    然后将QUARKUS_PROFILE环境变量设置staging为激活我的配置文件。

    以编程方式检查活动配置文件的正确方法是使用的getActiveProfile方法io.quarkus.runtime.configuration.ProfileManager

    使用@ConfigProperty("quarkus.profile")无法正常工作。

    6.1.默认运行时配置文件

    默认的Quarkus应用程序运行时配置文件设置为用于构建应用程序的配置文件。例如:

    ./mvnw package -Pnative -Dquarkus.profile=prod-aws
    ./target/my-app-1.0-runner 

    该命令将与prod-aws配置文件一起运行。可以使用quarkus.profilesystem属性来覆盖它。

    7.使用属性表达式

    Quarkus支持在application.properties文件中使用属性表达式。

    读取属性后,将解析这些表达式。因此,如果您的配置属性是构建时配置属性,则该属性表达式将在构建时解析。如果您的配置属性在运行时可覆盖,则该属性表达式将在运行时解析。

    您可以将属性表达式同时用于Quarkus配置或您自己的配置属性。

    属性表达式是通过以下方式定义的:${my-property-expression}

    例如,具有以下属性:

    remote.host=quarkus.io

    另一个属性定义为:

    callable.url=https://${remote.host}/

    将导致该callable.url属性的值设置为:

    callable.url=https://quarkus.io/

    另一个示例是根据所使用的概要文件定义不同的数据库服务器:

    %dev.quarkus.datasource.jdbc.url=jdbc:mysql://localhost:3306/mydatabase?useSSL=false
    quarkus.datasource.jdbc.url=jdbc:mysql://remotehost:3306/mydatabase?useSSL=false

    可以通过以下方式简化:

    %dev.application.server=localhost
    application.server=remotehost
    
    quarkus.datasource.jdbc.url=jdbc:mysql://${application.server}:3306/mydatabase?useSSL=false

    在此示例中,它的确增加了一行,但是的值application.server可以在其他属性中重用,从而减少了输入错误的可能性,并在属性定义中提供了更大的灵活性。

    8.组合属性表达式和环境变量

    Quarkus还支持属性表达式和环境变量的组合。

    假设您在中定义了以下属性application.properties

    remote.host=quarkus.io

    您可以通过如下定义属性来组合环境变量和属性表达式:

    application.host=${HOST:${remote.host}}

    如果未设置,这将扩展HOST环境变量并将属性的值remote.host用作默认值HOST

    出于本节的目的,我们使用了remote.host之前定义的属性。必须注意,该值可以是固定值,例如:

    application.host=${HOST:localhost}

    localhost如果HOST未设置,它将作为默认值。

    9.配置Quarkus

    Quarkus本身是通过与您的应用程序相同的机制配置的。Quarkusquarkus.为自己的配置及其所有扩展的配置保留名称空间。例如,要配置HTTP服务器端口,可以quarkus.http.port在 中设置application.properties。所有Quarkus配置属性均已记录且可搜索。

    Quarkus在构建时进行大部分配置和引导,并且在构建期间读取并使用了一些配置属性。这些属性在构建时固定的,无法在运行时进行更改。您始终需要重新打包应用程序,以反映此类属性的更改。

    • 在构建时固定的属性用锁定图标标记()在所有配置选项的列表中

    但是,某些扩展程序确实定义了可以在运行时覆盖的属性。一个典型的例子是数据库URL,用户名和密码,这些仅在您的目标环境中才知道。这是一个权衡,因为可用的运行时属性越多,Quarkus可以进行的构建时间越少。因此,运行时属性列表是精简的。您可以使用以下机制(以降低的优先级)覆盖这些运行时属性:

    1. 系统属性
    2. 环境变量
    3. 名为环境文件.env放置在当前工作目录中
    4. 放置在其中的配置文件 $PWD/config/application.properties

    有关更多详细信息,请参见配置源。

    10.为应用程序生成配置

    也可以生成一个application.properties具有所有已知配置属性的示例,以使您轻松查看可用的Quarkus配置选项。为此,请运行:

    ./mvnw quarkus:generate-config

    这将创建一个src/main/resources/application.properties.example文件,其中包含通过您当前安装的扩展程序公开的所有配置选项。这些选项已被注释掉,并在适用时具有其默认值。例如,此HTTP端口配置条目将显示为:

    #
    # The HTTP port
    #
    #quarkus.http.port=8080

    除了生成示例配置文件之外,您还可以通过设置-Dfile 参数将它们添加到实际的配置文件中:

    ./mvnw quarkus:generate-config -Dfile=application.properties

    如果配置选项已经存在(有注释),则不会添加,因此在添加其他扩展名以查看添加了哪些其他选项之后可以安全地运行该配置选项。

    11.清除属性

    可以通过为属性分配一个空字符串来明确清除运行时属性,这些属性是可选的,并具有在构建时设置的值或具有默认值。请注意,这只会影响运行时属性,并且只能与不需要其值的属性一起使用。

    可以通过设置相应的application.properties属性,设置相应的系统属性或设置相应的环境变量来清除该属性。

    12.自定义配置

    12.1.定制配置源

    您还可以采用标准MicroProfile Config方式介绍自定义配置源。为此,您必须提供一个实现org.eclipse.microprofile.config.spi.ConfigSource 或的类org.eclipse.microprofile.config.spi.ConfigSourceProvider。为该类创建一个 服务文件,它将在应用程序启动时检测并安装。

    12.2.定制配置转换器

    您也可以将自定义类型用于配置值。这可以通过org.eclipse.microprofile.config.spi.Converter<T>META-INF/services/org.eclipse.microprofile.config.spi.Converter文件中实现并添加其完全限定的类名来完成。

    让我们假设您有一个像这样的自定义类型:

    package org.acme.config;
    
    public class MicroProfileCustomValue {
    
        private final int number;
    
        public MicroProfileCustomValue(int number) {
            this.number = number;
        }
    
        public int getNumber() {
            return number;
        }
    }

    相应的转换器如下所示。请注意,您的自定义转换器类必须是public并且必须具有public无参数构造函数。也一定不能abstract

    package org.acme.config;
    
    import org.eclipse.microprofile.config.spi.Converter;
    
    public class MicroProfileCustomValueConverter implements Converter<MicroProfileCustomValue> {
    
        @Override
        public MicroProfileCustomValue convert(String value) {
            return new MicroProfileCustomValue(Integer.parseInt(value));
        }
    }

    然后,您需要在服务文件中包含转换器的标准类名META-INF/services/org.eclipse.microprofile.config.spi.Converter。如果您有更多的转换器,也只需在其文件中添加它们的类名。每行一个全限定的类名,例如:

    org.acme.config.MicroProfileCustomValueConverter
    org.acme.config.SomeOtherConverter
    org.acme.config.YetAnotherConverter

    请注意,SomeOtherConverter并且YetAnotherConverter仅出于演示目的而添加了。如果在此文件中包含在运行时不可用的类,则转换器加载将失败。

    完成此操作后,您可以将自定义类型用作配置值:

    @ConfigProperty(name = "configuration.value.name")
    MicroProfileCustomValue value;

    12.2.1.转换器优先级

    在某些情况下,您可能希望使用自定义转换器来转换已经由其他转换器转换的类型。在这种情况下,您可以使用javax.annotation.Priority注释来更改转换器的优先级,并使自定义转换器的优先级高于列表中的其他转换器。

    默认情况下,如果@Priority在转换器上找不到No ,则其优先级为100,所有Quarkus核心转换器的优先级为200,因此,根据要替换的转换器,需要设置更高的值。

    为了演示这个想法,让我们实现一个自定义转换器,该转换器将优先MicroProfileCustomValueConverter于上一个示例中实现的转换器 。

    package org.acme.config;
    
    import javax.annotation.Priority;
    import org.eclipse.microprofile.config.spi.Converter;
    
    @Priority(150)
    public class MyCustomConverter implements Converter<MicroProfileCustomValue> {
    
        @Override
        public MicroProfileCustomValue convert(String value) {
    
            final int secretNumber;
            if (value.startsFrom("OBF:")) {
                secretNumber = Integer.parseInt(SecretDecoder.decode(value));
            } else {
                secretNumber = Integer.parseInt(value);
            }
    
            return new MicroProfileCustomValue(secretNumber);
        }
    }

    由于它会转换相同的值类型(即MicroProfileCustomValue),并且优先级为150,因此将使用MicroProfileCustomValueConverter默认优先级为100的a代替它。

    此新转换器还需要在服务文件中列出,即META-INF/services/org.eclipse.microprofile.config.spi.Converter

    13. YAML配置

    13.1.添加YAML配置支持

    您可能要对属性使用YAML进行配置。由于SmallRye Config带来了对YAML配置的支持,因此Quarkus也支持这一点。

    首先,您需要将Config YAML扩展名添加到您的pom.xml

    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-config-yaml</artifactId>
    </dependency>

    或者,您也可以在包含Quarkus项目的目录中运行以下命令:

    ./mvnw quarkus:add-extension -Dextensions="config-yaml"

    现在,Quarkus可以读取YAML配置文件。配置目录和优先级与以前相同。

    Quarkus将选择application.yaml一个application.properties。YAML文件只是配置应用程序的另一种方法。您应该确定并保留一种配置类型,以避免出现错误。

    13.1.1.配置实例

    # YAML supports comments
    quarkus:
      datasource:
        db-kind: postgresql
        jdbc:
          url: jdbc:postgresql://localhost:5432/some-database
        username: quarkus
        password: quarkus
    
    # REST Client configuration property
    org:
      acme:
        restclient:
          CountriesService/mp-rest/url: https://restcountries.eu/rest
    
    # For configuration property names that use quotes, do not split the string inside the quotes.
    quarkus:
      log:
        category:
          "io.quarkus.category":
            level: INFO

    Quarkus还支持使用application.yml作为YAML文件的名称。该文件与的规则相同application.yaml

    13.2.取决于配置文件的配置

    与属性一样,通过YAML提供与配置文件相关的配置也是如此。%profile在定义键/值对之前,只需添加包装在引号中的引号即可:

    "%dev":
      quarkus:
        datasource:
          db-kind: postgresql
          jdbc:
            url: jdbc:postgresql://localhost:5432/some-database
          username: quarkus
          password: quarkus

    13.3.配置密钥冲突

    MicroProfile Config规范将配置键定义为一个任意的,.分隔字符串。但是,像YAML这样的结构化格式天真的仅支持可能的配置名称空间的子集。例如,考虑两个配置属性quarkus.http.corsquarkus.http.cors.methods。一个属性是另一个属性的前缀,因此如何在您的YAML配置中同时指定两个密钥可能并不明显。

    这可以通过对任何YAML属性使用null键(通常由表示~)来解决,该键是另一个前缀。这是一个例子:

    解决与前缀相关的键名冲突的示例YAML配置

    quarkus:
      http:
        cors:
          ~: true
          methods: GET,PUT,POST

    通常,nullYAML密钥不包括在配置属性名称的汇编中,从而允许它们在任何级别上用于消除配置密钥的歧义。

    14.有关如何配置的更多信息

    Quarkus依赖SmallRye Config并继承其功能。

    SmallRye Config提供:

    • 其他配置源
    • 附加转换器
    • 索引属性
    • 家长资料
    • 用于配置值解析的拦截器
    • 重新定位配置属性
    • 后备配置属性
    • 记录中
    • 隐藏秘密
    ©著作权归作者所有,转载或内容合作请联系作者
    • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
      沈念sama阅读 217,277评论 6 503
    • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
      沈念sama阅读 92,689评论 3 393
    • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
      开封第一讲书人阅读 163,624评论 0 353
    • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
      开封第一讲书人阅读 58,356评论 1 293
    • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
      茶点故事阅读 67,402评论 6 392
    • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
      开封第一讲书人阅读 51,292评论 1 301
    • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
      沈念sama阅读 40,135评论 3 418
    • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
      开封第一讲书人阅读 38,992评论 0 275
    • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
      沈念sama阅读 45,429评论 1 314
    • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
      茶点故事阅读 37,636评论 3 334
    • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
      茶点故事阅读 39,785评论 1 348
    • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
      沈念sama阅读 35,492评论 5 345
    • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
      茶点故事阅读 41,092评论 3 328
    • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
      开封第一讲书人阅读 31,723评论 0 22
    • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
      开封第一讲书人阅读 32,858评论 1 269
    • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
      沈念sama阅读 47,891评论 2 370
    • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
      茶点故事阅读 44,713评论 2 354

    推荐阅读更多精彩内容