四、SpringCloud-服务配置-2

服务配置

一、服务配置中⼼介绍

⾸先我们来看⼀下,微服务架构下关于配置⽂件的⼀些问题:

  1. 配置⽂件相对分散。在⼀个微服务架构下,配置⽂件会随着微服务的增多变的越来越多,⽽且分散在各个微服 务中,不好统⼀配置和管理。

  2. 配置⽂件⽆法区分环境。微服务项⽬可能会有多个环境。例如:测试环境、预发布环境、⽣产环境。每⼀个环 境所使⽤的配置理论上都是不同的,⼀旦需要修改,就需要我们去各个微服务下⼿动维护,这⽐较困难。

  3. 配置⽂件⽆法实时更新。我们修改了配置⽂件之后,必须重新启动微服务才能使配置⽣效,这对⼀个正在运⾏ 的项⽬来说是⾮常不友好的。

基于上⾯这些问题,我们就需要配置中⼼的加⼊来解决这些问题。

配置中⼼的思路是:

  1. ⾸先把项⽬中各种配置全部都放到⼀个集中的地⽅进⾏统⼀管理,并提供⼀套标准的接⼝。

  2. 当各个服务需要获取配置的时候,就来配置中⼼的接⼝拉取⾃⼰的配置。

  3. 当配置中⼼中的各种参数有更新的时候,也能通知到各个服务实时的过来同步最新的信息,使之动态更新。 当加⼊了服务配置中⼼之后,我们的系统架构图会变成下⾯这样:

image-20230825165419917

在业界常⻅的服务配置中⼼,有下⾯这些:

  • Apollo

Apollo 是由携程开源的分布式配置中⼼。特点有很多,⽐如:

配置更新之后可以实时⽣效,⽀持灰度发布功能,并且能对所有的配置进⾏版本管理、操作审计等功能,提供开放 平台API。并且资料也写的很详细。

  • Disconf

Disconf 是由百度开源的分布式配置中⼼。它是基于 Zookeeper 来实现配置变更后实时通知和⽣效的。

  • SpringCloud Config

这是 Spring Cloud 中带的配置中⼼组件。它和 Spring 是⽆缝集成,使⽤起来⾮常⽅便,并且它的配置存储⽀ 持 Git 。

不过它没有可视化的操作界⾯,配置的⽣效也不是实时的,需要重启或去刷新。

  • Nacos

这是 SpingCloud alibaba 技术栈中的⼀个组件,前⾯我们已经使⽤它做过服务注册中⼼。其实它也集成了服务 配置的功能,我们可以直接使⽤它作为服务配置中⼼。

二、Nacos Config 介绍

Nacos 是阿⾥巴巴开源的⼀个更易于构建云原⽣应⽤的动态服务发现、配置管理和服务管理平台。 NacosConfig 就是⼀个类似于 SpringCloud Config 的配置中⼼。

使⽤ nacos 作为配置中⼼,其实就是将 nacos 当 做⼀个服务端,将各个微服务看成是客户端,我们将各个微服务的配置⽂件统⼀存放在 nacos 上,然后各个微服务从 nacos 上拉取配置即可。

官⽅⽂档:https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-config

Nacos 提供⽤于存储配置和其他元数据的 key/value 存储,为分布式系统中的外部化配置提供服务器端和客户 端⽀持。使⽤ Spring Cloud Alibaba Nacos Config ,您可以在 Nacos Server 集中管理你 Spring Cloud 应⽤的外部属性配置。

Spring Cloud Alibaba Nacos Config 是 Config Server 和 Client 的替代⽅案,客户端和服务器上的概 念与 Spring Environment 和 PropertySource 有着⼀致的抽象,在特殊的 bootstrap 阶段,配置被加载到 Spring 环境中。当应⽤程序通过部署管道从开发到测试再到⽣产时,您可以管理这些环境之间的配置,并确保应 ⽤程序具有迁移时需要运⾏的所有内容。

三、Nacos Config ⼊⻔

使⽤ nacos 作为配置中⼼,其实就是将 nacos 当做⼀个服务端,将各个微服务看成是客户端,我们将各个微服 务的配置⽂件统⼀存放在 nacos 上,然后各个微服务从 nacos 上拉取配置即可。

创建发布

image-20230825170722195

image-20230825170540013

搭建项目

image-20230825170823578

引⼊依赖

<dependencies>
    <!-- 配置中心 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>

    <!-- 启动时自动注入配置 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>

    <!-- Web依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 发现服务 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>

添加 config 的配置

Data ID: nacos-config.properties

Group : DEFAULT_GROUP

配置格式: Properties

配置内容: user.name=nacos-config-properties
 user.age=90

注意: dataid 是以 properties (默认的⽂件扩展名⽅式)为扩展名。

书写配置⽂件

在运⾏此 Example 之前, 必须使⽤ bootstrap.properties 配置⽂件来配置 Nacos Server 地址,例如:

spring.application.name=nacos-config ##这⾥名字必须同 DataID⼀致

spring.cloud.nacos.config.server-addr=192.168.65.3:8848

spring.cloud.nacos.config.namespace=0e2f3694-d3ef-4a39-8aa6-5622872228ce ##这⾥如果是public的请不要书写

application.yml

server:
  port: 8031

启动类

@SpringBootApplication
public class SpringCloudExampleConfigApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringCloudExampleConfigApplication.class, args);
        String s1 = context.getEnvironment().getProperty("user.name");
        String s2 = context.getEnvironment().getProperty("user.age");
        System.out.println(s1 + "," + s2);
    }
}

测试结果

出现错误

image-20230825164326463

修改(注册一下)

server:
  port: 8031
spring:
  cloud:
    nacos:
      discovery:
        namespace: 0e2f3694-d3ef-4a39-8aa6-5622872228ce
        server-addr: 192.168.65.3:8848

再次测试

image-20230825164605721

四、Nacos Config 深⼊

基于 yaml 的⽂件扩展名配置⽅式

以 bootstrap.properties ⽅式引导

spring-cloud-starter-alibaba-nacos-config 对于 yaml 格式也是完美⽀持的。这个时候只需要完成以下 两步:

1、在应⽤的 bootstrap.properties 配置⽂件中显示的声明 dataId ⽂件扩展名。如下所示

spring.cloud.nacos.config.file-extension=yaml

2、在 Nacos 的控制台新增⼀个 dataIdyaml 为扩展名的配置,如下所示:

image-20230825180218089

这两步完成后,重启测试程序,可以看到如下输出结果。

image-20230825180245437

bootstrap.yml ⽅式

注:将刚才的bootstrap.properties删除

点击配置列表,点击右边+号,新建配置。在新建配置过程中,要注意下⾯的细节:

  1. Data ID 不能随便写,要跟配置⽂件中的对应,对应关系如图所示
  2. 配置⽂件格式要跟配置⽂件的格式对应,且⽬前仅仅⽀持 YAML 和 Properties
  3. 配置内容按照上⾯选定的格式书写

image-20230825181019332

重启测试程序,可以看到如下输出结果:

image-20230825181159645

注意:如果使⽤默认命名空间请不要书写!

配置动态刷新

在⼊⻔案例中,我们实现了配置的远程存放,但是此时如果修改了配置,我们的程序是⽆法读取到的,因此,我们 需要开启配置的动态刷新功能。

硬编码⽅式

spring-cloud-starter-alibaba-nacos-config 也⽀持配置的动态更新,启动 Spring Boot 应⽤测试的代 码如下:

@SpringBootApplication
public class SpringCloudExampleConfigApplication {
    @SneakyThrows
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringCloudExampleConfigApplication.class, args);
//        String s1 = context.getEnvironment().getProperty("user.name");
//        String s2 = context.getEnvironment().getProperty("user.age");
//        System.out.println(s1 + "," + s2);

        while (true) {
            String s1 = context.getEnvironment().getProperty("user.name");
            String s2 = context.getEnvironment().getProperty("user.age");
            System.out.println(s1 + "," + s2);
            Thread.sleep(1000);
        }
    }
}

注意:你可以通过配置 spring.cloud.nacos.config.refresh.enabled=false关闭动态刷新

application.yml

server:
  port: 8031
spring:
  cloud:
    nacos:
      discovery:
        namespace: 0e2f3694-d3ef-4a39-8aa6-5622872228ce
        server-addr: 192.168.65.3:8848
        refresh-enabled: true  #是否支持自动刷新

image-20230825182502705

image-20230825182603739

动态刷新成功!

image-20230825182633958

注解⽅式

注释其内容

@SpringBootApplication
public class SpringCloudExampleConfigApplication {
    @SneakyThrows
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringCloudExampleConfigApplication.class, args);
//        String s1 = context.getEnvironment().getProperty("user.name");
//        String s2 = context.getEnvironment().getProperty("user.age");
//        System.out.println(s1 + "," + s2);

//        while (true) {
//            String s1 = context.getEnvironment().getProperty("user.name");
//            String s2 = context.getEnvironment().getProperty("user.age");
//            System.out.println(s1 + "," + s2);
//            Thread.sleep(1000);
//        }
    }
}

controller包

@Slf4j
@RequestMapping("/config")
@RestController
@RefreshScope   //只需要在需要动态读取配置的类上添加此注解就可以
public class ConfigController {

    @Value("${user.name}")
    private String name;
    @Value("${user.age}")
    private Integer age;

    @GetMapping("/a")
    public String a() {
        log.info("name:{},age:{}", name, age);
        return "name:" + name + ",age:" + age;
    }
}

当变更 user.name 时,应⽤程序中能够获取到最新的值。

测试结果

image-20230825184131474

改变内容

image-20230825185346109

注意格式:填写错误会报异常

再次测试

image-20230825185315058

可⽀持profile粒度的配置

spring-cloud-starter-alibaba-nacos-config 在加载配置的时候,

不仅仅加载了以 dataid s p r i n g . a p p l i c a t i o n . n a m e . {spring.application.name}. spring.application.name.{file-extension:properties} 为前缀的基础配置,

还加载了 dataid s p r i n g . a p p l i c a t i o n . n a m e − {spring.application.name}- spring.application.name{profile}.${file-extension:properties} 的基础配置。

如果想在同⼀个微服务的不同环境之间实现配置共享,其实很简单。只需要提取⼀个以 spring.application.name 命名的配置⽂件,通过 Spring 提供的 ${spring.profiles.active} 这个配置项来配置。,然后将其所有环境的公共配置放在⾥⾯即可。

新增配置 Nacos 上新增三个 ,如下所示:

image-20230825204454057

image-20230825204530818

image-20230825202306563

image-20230825204558195

bootstrap.yml 配置

1、

spring:
 profiles:
 active: dev

重启项目,页面刷新

image-20230825203924235

2、

spring:
 profiles:
 active: test

重启项目,页面刷新

image-20230825204042745

3、

spring:
 profiles:
 active: product

image-20230825204156755

⽀持⾃定义 namespace 的配置

⾸先看⼀下 Nacos 的 Namespace 的概念:

⽤于进⾏租户粒度的配置隔离。不同的命名空间下,可以存在相同的 Group 或 Data ID 的配 置。 Namespace 的常⽤场景之⼀是不同环境的配置的区分隔离,例如开发测试环境和⽣产环境的资源(如配 置、服务)隔离等。

在没有明确指定 ${spring.cloud.nacos.config.namespace} 配置的情况下, 默认使⽤的是 Nacos 上 Public 这个 namespae 。如果需要使⽤⾃定义的命名空间,可以通过以下配置来实现:

spring.cloud.nacos.config.namespace=0e2f3694-d3ef-4a39-8aa6-5622872228ce

需要注意的是:该配置必须放在 bootstrap.properties|yaml ⽂件中。此外 spring.cloud.nacos.config.namespace 的值是 namespace 对应的 id , id 值可以在 Nacos 的控制 台获取。并且在添加配置时注意不要选择其他的 namespae ,否则将会导致读取不到正确的配置。

⽀持⾃定义 Group 的配置

在没有明确指定 ${spring.cloud.nacos.config.group} 配置的情况下, 默认使⽤的是 DEFAULT_GROUP 。如 果需要⾃定义⾃⼰的 Group ,可以通过以下配置来实现:

spring.cloud.nacos.config.group=DEVELOP_GROUP

需要注意的是:该配置必须放在 bootstrap.properties|yaml ⽂件中。并且在添加配置时 Group 的值⼀ 定要和 spring.cloud.nacos.config.group 的配置值⼀致。

image-20230825221107560

spring:
  application:
    name: nacos-config
  cloud:
    nacos:
      config:
        namespace: 0e2f3694-d3ef-4a39-8aa6-5622872228ce
        server-addr: 192.168.65.3:8848
        file-extension: yaml
        refresh-enabled: true
        group: TEMA01
#  profiles:
#    active: dev

测试结果:

image-20230825221741743

⽀持⾃定义扩展的 Data Id 配置

当配置越来越多的时候,我们就发现有很多配置是重复的,这时候就考虑可不可以将公共配置⽂件提取出来,然后 实现共享呢?当然是可以的。接下来我们就来探讨如何实现这⼀功能。⼀般我们通过两种⽅式实现:

  • 拓展配置
  • 共享配置
拓展配置

Spring Cloud Alibaba Nacos Config 从 0.2.1 版本后,可⽀持⾃定义 Data Id 的配置。 ⼀个完整的配置案 例如下所示:

spring.application.name=opensource-service-provider
spring.cloud.nacos.config.server-addr=127.0.0.1:8848

# config external configuration
# 1Data Id 在默认的组 DEFAULT_GROUP,不⽀持配置的动态刷新
spring.cloud.nacos.config.extension-configs[0].data-id=ext-config-common01.properties

# 2Data Id 不在默认的组,不⽀持动态刷新
spring.cloud.nacos.config.extension-configs[1].data-id=ext-config-common02.properties
spring.cloud.nacos.config.extension-configs[1].group=GLOBALE_GROUP

# 3Data Id 不在默认的组,⽀持动态刷新
spring.cloud.nacos.config.extension-configs[2].data-id=ext-config-common03.properties
spring.cloud.nacos.config.extension-configs[2].group=REFRESH_GROUP
spring.cloud.nacos.config.extension-configs[2].refresh=true

可以看到:

  • 通过 spring.cloud.nacos.config.extension-configs[n].data-id 的配置⽅式来⽀持多个 Data Id 的配置。
  • 通过 spring.cloud.nacos.config.extension-configs[n].group 的配置⽅式⾃定义 Data Id 所在的 组,不明确配置的话,默认是 DEFAULT_GROUP
  • 通过 spring.cloud.nacos.config.extension-configs[n].refresh 的配置⽅式来控制该 Data Id 在 配置变更时,是否⽀持应⽤中可动态刷新, 感知到最新的配置值。默认是不⽀持的。

yaml 格式的应写成示例:

1、先创建

image-20230825223642772

controller包

image-20230825224046884

@Slf4j
@RequestMapping("/config")
@RestController
@RefreshScope   //只需要在需要动态读取配置的类上添加此注解就可以
public class ConfigController {

    @Value("${user.name}")
    private String name;
    @Value("${user.age}")
    private Integer age;

    @Value("${student.name}")
    private String stuName;

    @GetMapping("/a")
    public String a() {
        log.info("name:{},age:{}", name, age);
        return "name:" + name + ",age:" + age;
    }

    @GetMapping("/b")
    public String b() {
        log.info("name:{}", stuName);
        return "name:" + stuName;
    }
}

bootstrap.yml

image-20230825224431868

spring:
  application:
    name: nacos-config
  cloud:
    nacos:
      config:
        namespace: 0e2f3694-d3ef-4a39-8aa6-5622872228ce
        server-addr: 192.168.65.3:8848
        file-extension: yaml
        refresh-enabled: true
#        group: TEMA01
        extension-configs:
          - data-id: public-commons01.yaml
#  profiles:
#    active: dev

测试结果:

image-20230825224804287

2、先创建

image-20230825224918710

controller包

image-20230825225132382

@Slf4j
@RequestMapping("/config")
@RestController
@RefreshScope   //只需要在需要动态读取配置的类上添加此注解就可以
public class ConfigController {

    @Value("${user.name}")
    private String name;
    @Value("${user.age}")
    private Integer age;
    @Value("${student.name}")
    private String stuName;
    @Value("${person.name}")
    private String personName;

    @GetMapping("/a")
    public String a() {
        log.info("name:{},age:{}", name, age);
        return "name:" + name + ",age:" + age;
    }

    @GetMapping("/b")
    public String b() {
        log.info("name:{},personName:{},allName:{}", stuName, personName);
        return "name:" + stuName + ",personName:" + personName;
    }
}

bootstrap.yml

image-20230825225544345

spring:
  application:
    name: nacos-config
  cloud:
    nacos:
      config:
        namespace: 0e2f3694-d3ef-4a39-8aa6-5622872228ce
        server-addr: 192.168.65.3:8848
        file-extension: yaml
        refresh-enabled: true
#          group: TEMA01
        extension-configs:
          - data-id: public-commons01.yaml
          - data-id: abcde-commons02.yaml
#  profiles:
#    active: dev

测试结果

image-20230825225522376

3、先创建(动态刷新)

image-20230825225951700

controller包

image-20230825225900743

spring:
  application:
    name: nacos-config
  cloud:
    nacos:
      config:
        namespace: 0e2f3694-d3ef-4a39-8aa6-5622872228ce
        server-addr: 192.168.65.3:8848
        file-extension: yaml
        refresh-enabled: true
#          group: TEMA01
        extension-configs:
          - data-id: public-commons01.yaml
            group: DEFAULT_GROUP
            refresh: true
          - data-id: abcde-commons02.yaml
            refresh: false
#  profiles:
#    active: dev

测试1:支持动态刷新

image-20230825230025425

image-20230825230106830

测试2:不支持动态刷新

image-20230825230145528

如果是 yaml 格式的应写成:

image-20230825230414761

多个 Data Id 同时配置时,他的优先级关系是 spring.cloud.nacos.config.extension-configs[n].data-id 其中 n 的值越⼤,优先级越⾼。

spring.cloud.nacos.config.extension-configs[n].data-id 的值必须带⽂件扩展名,⽂件扩展名既 可⽀持 properties ,⼜可以⽀持 yaml/yml 。 此时 spring.cloud.nacos.config.file-extension 的配置对⾃定义扩展配置的 Data Id ⽂件扩展名没有影响。

共享配置

不同为服务之间实现配置共享的原理类似于⽂件引⼊,就是定义⼀个公共配置,然后在当前配置中引⼊。

先新建

image-20230825230822322

all:
 name: 所有⼈
 age: 29

controller包

image-20230825231005440

@Slf4j
@RequestMapping("/config")
@RestController
@RefreshScope   //只需要在需要动态读取配置的类上添加此注解就可以
public class ConfigController {

    @Value("${user.name}")
    private String name;
    @Value("${user.age}")
    private Integer age;
    @Value("${student.name}")
    private String stuName;
    @Value("${person.name}")
    private String personName;
    @Value("${all.name}")
    private String allName;

    @GetMapping("/a")
    public String a() {
        log.info("name:{},age:{}", name, age);
        return "name:" + name + ",age:" + age;
    }

    @GetMapping("/b")
    public String b() {
        log.info("name:{},personName:{},allName:{}", stuName, personName,allName);
        return "name:" + stuName + ",personName:" + personName+",allName:"+allName;
    }
}

bootstrap.yml

image-20230825231241303

spring:
  application:
    name: nacos-config
  cloud:
    nacos:
      config:
        namespace: 0e2f3694-d3ef-4a39-8aa6-5622872228ce
        server-addr: 192.168.65.3:8848
        file-extension: yaml
        refresh-enabled: true
        #          group: TEMA01
        extension-configs:
          - data-id: public-commons01.yaml
            group: DEFAULT_GROUP
            refresh: true
          - data-id: abcde-commons02.yaml
            refresh: false
        shared-configs:
          - data-id: weilai-all.yaml
#  profiles:
#    active: dev

测试结果

image-20230825231332465

两种区别

⽇常开发中,多个模块可能会有很多共⽤的配置,⽐如数据库连接信息, Redis 连接信息, RocketMQ 连接信 息,监控配置等等。那么此时,我们就希望可以加载多个配置,多个项⽬共享同⼀个配置之类等功能, Nacos Config 也确实⽀持。

  • Nacos 在配置路径 spring.cloud.nacos.config.extension-config 下,允许我们指定⼀个或多个额外配置。
  • Nacos 在配置路径 spring.cloud.nacos.config.shared-configs 下,允许我们指定⼀个或多个共享配置。

上述两类配置都⽀持三个属性: data-idgroup (默认为字符串 DEFAULT_GROUP )、 refresh (默认为 true )。 实际上,Nacos中并未对 extension-configsshared-configs 的差别进⾏详细阐述。我们从他们的结构, 看不出本质差别;除了优先级不同以外,也没有其他差别。⼀般来说,要在各应⽤之间共享⼀个配置,请使⽤上⾯的 shared-configs 。如果要在特定范围内(⽐如某个应⽤上)覆盖某个共享 dataId 上的特定属性,请使⽤ extension-config

配置的优先级

Spring Cloud Alibaba Nacos Config ⽬前提供了三种配置能⼒从 Nacos 拉取相关的配置。

  • A: 通过 spring.cloud.nacos.config.shared-configs[n].data-id ⽀持多个共享 Data Id 的配置。
  • B: 通过 spring.cloud.nacos.config.extension-configs[n].data-id 的⽅式⽀持多个扩展 Data Id 的配置。
  • C: 通过内部相关规则(应⽤名、应⽤名+ Profile )⾃动⽣成相关的 Data Id 配置。

当三种⽅式共同使⽤时,他们的⼀个优先级关系是:A < B < C。

完全关闭配置

通过设置 spring.cloud.nacos.config.enabled = false 来完全关闭 Spring Cloud Nacos Config

nacos的⼏个概念
命名空间(Namespace)

命名空间可⽤于进⾏不同环境的配置隔离。⼀般⼀个环境划分到⼀个命名空间。

配置分组(Group)

配置分组⽤于将不同的服务可以归类到同⼀分组。⼀般将⼀个项⽬的配置分到⼀组。

配置集(Data ID)

在系统中,⼀个配置⽂件通常就是⼀个配置集。⼀般微服务的配置就是⼀个配置集

说,要在各应⽤之间共享⼀个配置,请使⽤上⾯的 shared-configs 。如果要在特定范围内(⽐如某个应⽤上)覆盖某个共享 dataId 上的特定属性,请使⽤ extension-config

配置的优先级

Spring Cloud Alibaba Nacos Config ⽬前提供了三种配置能⼒从 Nacos 拉取相关的配置。

  • A: 通过 spring.cloud.nacos.config.shared-configs[n].data-id ⽀持多个共享 Data Id 的配置。
  • B: 通过 spring.cloud.nacos.config.extension-configs[n].data-id 的⽅式⽀持多个扩展 Data Id 的配置。
  • C: 通过内部相关规则(应⽤名、应⽤名+ Profile )⾃动⽣成相关的 Data Id 配置。

当三种⽅式共同使⽤时,他们的⼀个优先级关系是:A < B < C。

完全关闭配置

通过设置 spring.cloud.nacos.config.enabled = false 来完全关闭 Spring Cloud Nacos Config

nacos的⼏个概念
命名空间(Namespace)

命名空间可⽤于进⾏不同环境的配置隔离。⼀般⼀个环境划分到⼀个命名空间。

配置分组(Group)

配置分组⽤于将不同的服务可以归类到同⼀分组。⼀般将⼀个项⽬的配置分到⼀组。

配置集(Data ID)

在系统中,⼀个配置⽂件通常就是⼀个配置集。⼀般微服务的配置就是⼀个配置集

image-20230825231906635

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值