Spring Boot yml 文件读取:@ConfigurationProperties 、@EnableConfigurationProperties、@Value、Environment

1、YAML 是 JSON 的一个超集,也是一种方便配置层次结构数据的格式。只要将 SnakeYAML 库放到 classpath 下, Spring 应用就会自动支持 YAML,以作为 Properties 属性文件的替换。Spring boot 应用(spring-boot-starter) ,默认包含了 SnakeYAML 库。

@ConfigurationProperties + @Component 取值

1、使用 @ConfigurationProperties 注解,可以将 "application.yml" 配置文件中的键-值自动映射注入 Java Bean 中,Java bean 的属性必须提供 setter 方法才能注入值。

1)@ConfigurationProperties 可以标注在 类、接口、枚举、注解、方法上

2)所在的类需要是 Spring 组件(@Component ),因为只有是容器中的组件,容器才会为 @ConfigurationProperties 提供此注入功能。

3)prefix 属性:用于将配置文件中指定的 key 下的所有属性与本类属性进行一一映射注入值,如果配置文件中不存在此 key,则不会为POJO注入值。如 prefix="user"、prefix="app.user" 。

4)ignoreInvalidFields 属性:是否忽略无效的字段,默认为 false 表示不忽略。配置的属性值类型错误,无法强转的字段就是无效字段,比如 java bean 的属性类型是 Date,而 application.yml 中配置的值是 2020/12/35,则显然无法转为 Date 类型,此时如果此属性为 false,则启动报错,无法正常启动应用,为 true 时则会忽略注入。

5)如果配置文件中没有为 java bean 配置相应的属性值,则不会注入值,java bean 属性仍然使用自己的默认值,不会影响程序启动。

2、如下演示通过 yml 文件为 java bean 对象属性注入值,User 对象中关联 Dog 对象,先提供 java bean 如下,基本涵盖了 YAML 各种语法

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author wangMaoXiong
 * Created by Administrator on 2018/7/11 0011.
 * <p>
 * 用户···实体
 * @ConfigurationProperties 表示 告诉 Spring Boot 将本类中的所有属性和配置文件中相关的配置进行绑定;
 * prefix = "user" 表示 将配置文件中 key 为 user 的下面所有的属性与本类属性进行一一映射注入值,如果配置文件中
 * 不存在 "user" 的key,则不会为 POJO 注入值,属性值仍然为默认值
 * <p/>
 * @ConfigurationProperties (prefix = " user ") 默认从全局配置文件中获取值然后进行注入
 * @Component 将本类标识为一个Spring 组件,因为只有是容器中的组件,容器才会为 @ConfigurationProperties 提供此注入功能
 */
@Component
@ConfigurationProperties(prefix = "user")
public class UserProperties {
    private Integer id;
    /**
     * 如果 lastName 属性名称改为"name"时,注入的时候会强制变成计算机名称,而导致自己的值无法注入(原因未知)
     */
    private String lastName;
    private Integer age = 18;
    private Date birthday;
    private List<String> colorList;
    private Map<String, String> cityMap;
    /**
     * 关联的 Dog 对象可以不加 @ConfigurationProperties 也会自动注入
     */
    private Dog dog;

   // 必须提供 getter、setter 方法
}

完整源码:UserProperties.javaDog.java

3、java bean 上写了 “@ConfigurationProperties(prefix = "user")” 注解后,如果提示 “Spring Boot配置文件注解处理器没有找到”,这时可以点击右上角的提示 “Opne Documention...”,进入官方文档拷贝依赖放入到 "pom.xml" 文件中即可。如下所示,它的作用是在写 yaml 文件时就会有提示了,会方便很多(当然没导入此依赖也不影响运行)。

    <!--导入配置文件处理器,配置文件属性值与Java Bean进行绑定时就会有提示-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>

4、application. yml 文件配置如下,key 值 user 要与 User 实体类上的 "@ConfigurationProperties(prefix = "user")" 对应,

配置文件中 user 下的 key 名称要与实体类 User 中的属性一致才能注入(下面基本涵盖了 YAML 所有的数据类型)。

#演示用 @ConfigurationProperties 为 java bean 属性注入值
user:
  id: 1200
  lastName: 杨戬
  #  age: 110  #未配置时,对象属性会使用自己的默认值
  birthday: 2018/07/11
  colorList: #数组类型
    - red
    - yellow
    - green
  cityMap: {mapK1: mapV1,mapK2: mapV2}  #map类型
  #对象类型
  dog:
    id: 9527
    name: 哮天犬
    age: 100

在线源码:src/main/resources/application.yml · 汪少棠/wmx-redis - Gitee.com

5、取值的时候注意:不能直接 new User() 对象取值,此时是没有注入的配置值的,可以使用 @Resource、@Autowired 注入 User 对象获取注入的配置值。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
 * ConfigurationProperties 为 java bean 注入属性值
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2020/9/25 9:18
 */
@RestController
public class PropertiesController {
    /**
     * 从容器中获取实例
     */
    @Resource
    private UserProperties userProperties;
    /**
     * http://localhost:8080/redisson/properties/getDefaultUser
     *
     * @return
     */
    @GetMapping("properties/getDefaultUser")
    public UserProperties getDefaultUser() {
        return userProperties;
    }
}

在线源码:PropertiesController.java 

@ConfigurationProperties + @Bean 取值

1、@ConfigurationProperties + @Component 的方式是直接将 java bean 标识为 Spring 组件,有的时候可能目标 java bean 只是纯粹的 java bean,并没有注解标识。此时可以使用 @Configuration + @Bean 组合将目标 java bean 添加到 Spring 容器中的同时,然后用 @ConfigurationProperties 为实例注入配置。

2、这也是 Spring boot 官方 org.springframework.boot.autoconfigure 包下各种 XxxAutoConfiguration 自动配置中常用的方式。

3、下面演示 @ConfigurationProperties 标识在方法上,使用 @ConfigurationProperties + @Bean 为一个原始的 java bean 注值。

一:提供 Java beanPersonProperties 对象关联 Dog,都是普通的 java 对象。

src/main/java/com/wmx/wmxredis/properties/PersonProperties.java · 汪少棠/wmx-redis - Gitee.com

src/main/java/com/wmx/wmxredis/properties/Dog.java · 汪少棠/wmx-redis - Gitee.com

public class PersonProperties {
    private Integer id;
    /**
     * 如果 lastName 属性名称改为"name"时,注入的时候会强制变成计算机名称,而导致自己的值无法注入(原因未知)
     */
    private String lastName;
    private Integer age = 18;
    private Date birthday;
    private List<String> colorList;
    private Map<String, String> cityMap;
    /**
     * 关联的 Dog 对象
     */
    private Dog dog;
   //省略 getter 、setter 方法
}

二:提供全局文件配置:在线源码 application.yml

#演示用 @ConfigurationProperties + @Bean为 java bean 属性注入值
person:
  id: 1301
  lastName: 二郎神
  #  age: 110  #未配置时,对象属性会使用自己的默认值
  birthday: 2018/07/11
  colorList: #数组类型
    - RED
    - yellow
    - GREEN
  cityMap: {mapK1: 长沙,mapK2: 深圳}  #map类型
  #对象类型
  dog:
    id: 9528
    name: 哮天犬
    age: 1088

三:提供配置类为实例注入属性:在线源码:PropertiesConfig.java

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * 配置类
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2020/9/25 10:11
 */
@Configuration
public class PropertiesConfig {
    /**
     * Bean :将对象实例交由 Spring 容器管理
     * ConfigurationProperties :为实例注入配置
     * 组合起来的效果就是:这个交由 Spring 容器管理的 PersonProperties 实例,自身被注入了全局配置文件中的配置值,
     * 然后可以在其他地方获取 PersonProperties 并使用
     *
     * @return
     */
    @ConfigurationProperties(prefix = "person")
    @Bean
    public PersonProperties personProperties() {
        return new PersonProperties();
    }
}

 四:取值测试:在线源码 PropertiesController.java

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
 * ConfigurationProperties 为 java bean 注入属性值
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2020/9/25 9:18
 */
@RestController
public class PropertiesController {
    @Resource
    private PersonProperties personProperties;
    /**
     * ConfigurationProperties + Bean 取值演示
     * <p>
     * http://localhost:8080/properties/getDefaultPerson
     *
     * @return
     */
    @GetMapping("properties/getDefaultPerson")
    public PersonProperties getDefaultPerson() {
        return personProperties;
    }
}

@ConfigurationProperties + @EnableConfigurationProperties 取值

1、使用场景:目标 Java bean 上配置了 @ConfigurationProperties 注解,但是并没有 @Component 注解,然后在 @Configuration 配置类中使用 @EnableConfigurationProperties 注解启用对 @ConfigurationProperties 注解的 bean 的支持,即将目标 java bean 添加到 Spring 容器中管理。

2、对比三种方式不难发现,不管是 @Component 或者 @Bean 还是 @EnableConfigurationProperties,@ConfigurationProperties 注入配置的前提是目标 java bean 必须交由 Spring 容器管理。

3、此种方式是 Spring boot 官方 org.springframework.boot.autoconfigure 包下各种 XxxAutoConfiguration 自动配置中最常用的方式,基本上随便打开一个都能看到,比如:KafkaAutoConfiguration、MongoDataAutoConfiguration、RedisAutoConfiguration 等等。

4、下面进行演示模仿 Spring boot 官方的此种方式。

一:提供 java bean :在线源码:StaffProperties.java

import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Date;
import java.util.List;
import java.util.Map;

@ConfigurationProperties(prefix = "staff")
public class StaffProperties {
    private Integer id;
    /**
     * 如果 lastName 属性名称改为"name"时,注入的时候会强制变成计算机名称,而导致自己的值无法注入(原因未知)
     */
    private String lastName;
    private Integer age = 18;
    private Date birthday;
    private List<String> colorList;
    private Map<String, String> cityMap;
    /**
     * 关联的 Dog 对象
     */
    private Dog dog;
    //省略 getter、setter 方法未粘贴
}

二:提供全局文件配置:在线源码 src/main/resources/application.yml · 汪少棠/wmx-redis - Gitee.com

#演示用 @ConfigurationProperties + @EnableConfigurationProperties 为 java bean 属性注入值
staff:
  id: 2305
  lastName: 二郎真君
  #  age: 110  #未配置时,对象属性会使用自己的默认值
  birthday: 1918/07/11
  colorList: #数组类型
    - RED
    - YELLOW
    - GREEN
  cityMap: {mapK1: 长沙麓谷,mapK2: 深圳科技园}  #map类型
  #对象类型
  dog:
    id: 952823
    name: 哮天犬
    age: 1088121

三:配置类:在线源码 StaffConfig.java

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
 * 配置类
 * EnableConfigurationProperties:将目标 java bean 配置类(ConfigurationProperties) 交由 Spring 容器管理
 * 而后可以在任意地方使用 取值使用.
 *
 */
@Configuration
@EnableConfigurationProperties(StaffProperties.class)
public class StaffConfig {
    /**
     * 可以参考官方的 KafkaAutoConfiguration 就知道,如果想在配置类中直接获取目标 java bean 属性配置类,
     * 则直接通过构造器传入即可,通常用于为创建其他 bean 时提供属性配置。这里不再继续深入,实际中参考官网一目了然。
     */
    private final StaffProperties properties;

    /**
     * 应用启动的时候会自动执行本来,完成赋值。
     *
     * @param properties
     */
    public StaffConfig(StaffProperties properties) {
        this.properties = properties;
        System.out.println("properties=" + this.properties);
    }
}

四:取值测试:在线源码 PropertiesController.java

@Value 取值注入

1、@Value 与 @ConfigurationProperties 注解都可以从全局配置文件中获取值然后注入到属性或者方法参数中。

2、以前在 Spring 核心配置文件 beans.xml 中用如下配置为某个类的属性注入值。

<!--以前在Spring核心配置文件beans.xml用如下配置为某个类的属性注入值-->
 <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="${jdbc.driverclass}"></property>
      <property name="url" value="${jdbc.url}"></property>
      <property name="username" value="root"></property>
      <property name="password" value="123456"></property>
 </bean>

3、现在直接使用 @Value (xxx) 注解就可以直接为某个属性或者方法的参数注入全局配置文件中的值,完全等价于上面。

@Value(“#{}”):表示 SpEl 表达式。
@Value(“${xxx}”):表示从配置文件中取值,如果 "xxx" key 不存在,则启动时报错:Injection of autowired dependencies failed
@Value(“${xxx:yyy}”):从配置文件中取值,如果 "xxx" key 不存在,则使用默认值 "yyy",否则启动会报错。

@Value("${xxx:}"):从配置文件中取值,如果 "xxx" key 不存在,则默认值为空 ""。

4、假如有 application.yml 文件如下:

# @Value 取值,自定义配置属性
app:
  info:
    str: 张三  #字符串类型
    int: 27    #数值类型
    date: 2018/07/11  #日期类型
    array1: 1,2,3,4,5                #数组类型
    array2: a,b,c,d,e
    arrayToList: 唐,宋,元,明,清
    arrayToSet: 大唐,大宋,大明,大唐
    cityMap: '{"mapK1": "长沙麓谷","mapK2": "深圳科技园"}'  #map类型,@Value 取值时,引号必须正确.

5、@Value 获取全局配置文件 application.yml 中的值为属性赋值(被赋值的属性可以不提供 setter 方法,但是所在的类必须被 Spring 容器管理)。

6、在线演示源码获取基本类型、List、数组、set、map 等等:

src/main/resources/application.yml · 汪少棠/java-se - Gitee.com

src/main/java/org/example/controller/ValueController.java · 汪少棠/java-se - Gitee.com

    /**
     * 获取服务端口,不配置时,默认为 80
     * [@Value] 注解在服务启动时就会自动注入,所以被标记的方法在服务启动时就会自动执行
     * @param dbDriver
     */
    @Value("${server.port:80}")
    private void getServerPort(String serverPort) {
        System.out.println("服务端口(serverPort)=" + serverPort);
    }

@Value VS @ConfigurationProperties

对比项    @ConfigurationProperties@Value
注解功能将配置文件中的属性值批量注入类的各个属性为类中的各个属性逐个赋值
松散绑定支持不支持
SpEL(Spring 表达式)不支持支持
JSR303数据校验支持不支持
复杂类型封装支持不支持
属性是否需要提供setter方法需要不需要

1、两者都可以从配置文件 *.yml 与 *.properties 中获取到值;

2、如果项目中只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用 @Value 即可;如果项目中专门编写了一个 JavaBean 来和配置文件进行映射,则直接使用 @ConfigurationProperties 即可;

3、Spring Boot 官方 org.springframework.boot.autoconfigure 包下的 XxxProperties 类都是使用 @ConfigurationProperties 注入属性值的,如:

Environment 获取配置属性

1、直接从容器中获取 Environment 实例, Spring 框架已经默认提供了.
   @Autowired
    private Environment environment;

2、然后就可以使用方法或者配置的属性值,特别注意:Environment 只能获取简单的数据类型 如数字、字符串等等,对于 Map、List、Set、数组 等等无法获取。

API方法描述
String getProperty(String key)获取全局配置文件中与给定 key 关联的属性值, 如果没有,则返回 null.
String getProperty(String key, String defaultValue)返回与给定键关联的属性值,如果无法解析该键,则返回 defaultValue。
boolean containsProperty(String key)

返回给定的属性key是否可用于解析,

String getRequiredProperty(String key)

返回与给定键关联的属性值,如果key无法解析,则 IllegalStateException。

src/main/resources/application.yml · 汪少棠/java-se - Gitee.com

src/main/java/org/example/controller/ValueController.java · 汪少棠/java-se - Gitee.com

YamlPropertiesFactoryBean 读取 yml 文件

1、某些时候想要读取整个 yml 中的内容,比如某个级次下面键值对(下级)内容后期可能是动态变化的,这是使用上面的方式进行写死,显然不合适了,唯一的缺点就是需要写死配置文件路径

2、Spring 框架提供两个便捷的类用于加载 YAML 文 档,YamlPropertiesFactoryBean 会将 YAML 加载为 Properties,YamlMapFactoryBean 会将 YAML 加载为 Map。

3、YamlMapFactoryBean 返回的 Map 结构以及顺序与 yml 中的原始内容完全一样,相当于一个深层次的 JSON 对象。YamlPropertiesFactoryBean 返回的 Properties 不保证与原内容顺序一致。

    @GetMapping("properties/yamlProperties2")
    public Map<String, Object> yamlProperties2() {
        YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
        // 类路径下的任意 yml 文件.
        yamlPropertiesFactoryBean.setResources(new ClassPathResource("application.yml"));
        // 转Properties对象,方便取值
        Properties properties = yamlPropertiesFactoryBean.getObject();
        // 遍历全部配置——方式2
        // 因为 Properties extends Hashtable,所以也可以使用遍历 Map 方式进行遍历
        // 返回的 Properties 中的键值对不保证和原内容的顺序一致,所以干脆使用 TreeMap 根据 key 的自然顺序进行排序.
        // yml 文件中没有设置的属性值的,这里的value是一个空字符串
        Map<String, Object> paramMap = new TreeMap<>();
        for (Map.Entry<Object, Object> entry : properties.entrySet()) {
            paramMap.put(entry.getKey().toString(), entry.getValue());
        }
        return paramMap;
    }
    @GetMapping("properties/yamlMapFactoryBean1")
    public Map<String, Object> yamlMapFactoryBean1() {
        YamlMapFactoryBean yamlMapFactoryBean = new YamlMapFactoryBean();
        // 类路径下的任意 yml 文件.
        yamlMapFactoryBean.setResources(new ClassPathResource("application.yml"));
        Map<String, Object> yamlMap = yamlMapFactoryBean.getObject();
        return yamlMap;
    }
ClassPathResource 只支持类路径下的xxx.yml文件,只能是相对路径

FileUrlResource 只支持绝对路径

yamlPropertiesFactoryBean.setResources(new FileUrlResource("E:\temp\xxx.yml"));

PathResource 即支持绝对路径,如 /app/xx.yml,也支持相对路径,如  ./xx.yml,../../xx.yml

yamlPropertiesFactoryBean.setResources(new PathResource(“./xx.yml”));

路径错误时,yamlPropertiesFactoryBean.getObject() 就会报错:

YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
Resource resource = StrUtil.isNotBlank(ymlPath) ? new PathResource(ymlPath) : new ClassPathResource("application.yml");
yamlPropertiesFactoryBean.setResources(resource);
Properties properties;
try {
    // ymlPath路径错误时,上面不会报错,这里才会报错.
    properties = yamlPropertiesFactoryBean.getObject();
} catch (Exception e) {
    Throwable causedBy = ExceptionUtil.getCausedBy(e, FileNotFoundException.class);
    if (causedBy != null) {
        throw new BasicException("Yml文件路径找不到,请检查是否配置正确:" + ymlPath, e);
    }
    throw e;
}

src/main/java/com/wmx/wmxredis/properties/PropertiesController.java · 汪少棠/wmx-redis - Gitee.com

4、因为 YamlPropertiesFactoryBean 返回的 Properties 中的 key-value 都只能是字符串类型,所以 yml 中的复杂类型需要特殊处理并返回,比如数组和map。

比如 xxx.yml 文件内容如下:
	user:
	  id: 1200
	  lastName: 杨戬
	  colorList: #数组类型
		- red
		- yellow
		- green
	  cityMap: {mapK1: mapV1,mapK2: mapV2}  #map类型

YamlPropertiesFactoryBean 返回值如下:
	"user.id": "1200"
	"user.lastName": "杨戬"
	"user.colorList[0]": "red"
	"user.colorList[1]": "yellow"
	"user.colorList[2]": "green"
	"user.cityMap.mapK2": "mapV2"
	"user.cityMap.mapK1": "mapV1"

YamlMapFactoryBean 返回值如下:
	{
		"user": {
			"birthday": "2018/07/11",
			"cityMap": {
				"mapK1": "mapV1",
				"mapK2": "mapV2"
			},
			"colorList": [
				"red",
				"yellow",
				"green"
			],
			"id": 1200,
			"lastName": "杨戬"
		}
	}
	

EnvironmentAware 读取 yml 配置

1、EnvironmentAware 接口由任何希望被通知其运行的{@link Environment}的bean实现。可轻松获取服务当前生效的全局application.yml文件的配置内容,而不用在指定配置文件路径。

/**
 * EnvironmentAware 接口由任何希望被通知其运行的{@link Environment}的bean实现。
 * 用于轻松获取服务当前生效的全局application.yml文件的配置内容。
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2023/9/10 17:47
 */
@Component
public class AppEnvironment implements EnvironmentAware {
    private final List<PropertySource> allPropertySource = new ArrayList<>();
    /**
     * 设置此组件运行的{@code Environment}
     * allPropertySource 塞进去的对象通常就是服务当前生效的全局 application.yml 文件资源对象,通常是一个。
     * 如:[OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.yml]'}]
     *
     * @param environment
     */
    @Override
    public void setEnvironment(Environment environment) {
        if (!allPropertySource.isEmpty()) {
            return;
        }
        if (environment instanceof AbstractEnvironment) {
            MutablePropertySources propertySources = ((AbstractEnvironment) environment).getPropertySources();
            for (PropertySource propertySource : propertySources) {
                if (propertySource instanceof OriginTrackedMapPropertySource) {
                    allPropertySource.add(propertySource);
                }
            }
        }
    }
    /**
     * 获取系统当前生效的全局 application.yml 的全部配置,而不用在指定具体的文件路径。
     * 通常服务部署运行时,读取都是jar包外部的配置,所以与位置无关,读取的都是服务当前使用的配置。
     *
     * <pre>
     *   不知道为什么,返回的value如果是Object类型,后台取值一切正常,但是返回给页面时,就会json解析异常而报错:
     *   SyntaxError: JSON.parse: end of data when ',' or ']' was expected at line 1 column 11141 of the JSON data
     *   猜测是和yml中配置了某些特殊内容有关,比如数组,因为当只留少数简单配置时是可以的。
     *   所以为了防止上述问题,value统一转字符串返回。
     * </pre>
     *
     * @return
     */
    public Map<String, String> getAllYmlConfig() {
        Map<String, String> configMap = new TreeMap<>();
        for (PropertySource propertySource : allPropertySource) {
            Object source = propertySource.getSource();
            if (source instanceof Map) {
                for (Map.Entry<String, Object> entry : ((Map<String, Object>) source).entrySet()) {
                    String key = entry.getKey();
                    Object value = entry.getValue();
                    configMap.put(key, value.toString());
                }
            }
        }
        return configMap;
    }
    /**
     * 读取指定 key 的配置。
     *
     * @param ymlKey :全局 yml 配置的 key 名称。
     * @return
     */
    public String getYmlByKey(String ymlKey) {
        return getAllYmlConfig().get(ymlKey);
    }
}

src/main/java/com/wmx/wmxredis/properties/PropertiesController.java · 汪少棠/wmx-redis - Gitee.comsrc/main/java/com/wmx/wmxredis/properties/AppEnvironment.java · 汪少棠/wmx-redis - Gitee.com

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蚩尤后裔-汪茂雄

芝兰生于深林,不以无人而不芳。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值