学习springBoot常用笔记

1.springboot简介

  springboot是一个简化spring的一个开发应用框架,是整一个sping技术栈的一个集合。J2EE开发的一站式解决方案。

 文档可以去官方网站查看。

 spring官方网站:https://spring.io/projects/spring-boot

springboot官方文档:https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#boot-features-external-config

2.Spring Boot HelloWorld

浏览器访问hello,服务器相应请求并返回Hello World字符。

1.导入相关的依赖

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐web</artifactId>
</dependency>
</dependencies>

2.编写主程序,启动springboot应用

/**
* @SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用
*/
@SpringBootApplication
public class HelloWorldMainApplication {
public static void main(String[] args) {
// Spring应用启动起来
SpringApplication.run(HelloWorldMainApplication.class,args);
}
}

3.编写controller

@Controller
public class HelloController {
@ResponseBody
@RequestMapping("/hello")
public String hello(){
return "Hello World!";
}
}

    springboot是一个非常简化的的部署应用框架。只需要再pom文件中导入打包插件

<!‐‐ 这个插件,可以将应用打包成一个可执行的jar包;‐‐>
<build>
  <plugins>
    <plugin>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring‐boot‐maven‐plugin</artifactId>
    </plugin>
  </plugins>
</build>

  4.springboot hello word的内部实现

              4.1 pom文件

              每一个springboot应用程序都会有这个spring‐boot‐starter‐parent资源。它的父级资源是管理整个springboot资源版本依赖,起到一个版本仲裁的作用。

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
他的父项目是下面这个资源依赖:真正管理Spring Boot应用里面的所有依赖版本;
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐dependencies</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath>../../spring‐boot‐dependencies</relativePath>
</parent>

         启动器

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐web</artifactId>
</dependency>

           spring-boot-starter:spring-boot场景启动器;帮我们导入了web模块正常运行所依赖的组件;
Spring Boot将所有的功能场景都抽取出来,做成一个个的starters(启动器),只需要在项目里面引入这些starter
相关场景的所有依赖都会导入进来。要用什么功能就导入什么场景的启动器

      4.2 springboot主启动类

  

/**
* @SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用
*/
@SpringBootApplication
 public class HelloWorldMainApplication {
    public static void main(String[] args) {
    // Spring应用启动起来
    SpringApplication.run(HelloWorldMainApplication.class,args);
   }
}

    @SpringBootApplication:是配置某一个类,说明这个类是这个应用的主配置类。运行该类里面的main方法

     

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

 @SpringBootConfiguration:Spring Boot的配置类;
标注在某个类上,表示这是一个Spring Boot的配置类;
        @Configuration:配置类上来标注这个注解;
        配置类 ----- 配置文件;配置类也是容器中的一个组件;@Component

@EnableAutoConfiguration:开启自动配置功能;
       使用spring的时候,是需要配置很多的配置文件,Spring Boot帮我们自动配置;@EnableAutoConfiguration告诉SpringBoot开启自
       动配置功能;这样自动配置才能生效;

@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

       @AutoConfigurationPackage:自动配置包

                  @Import(AutoConfigurationPackages.Registrar.class):
                  Spring的底层注解@Import,给容器中导入一个组件;导入的组件由 AutoConfigurationPackages.Registrar.class;
                  将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器;
                 @Import(EnableAutoConfigurationImportSelector.class);

          有了自动配置类的操作,就可以省去手动配置的麻烦

         EnableAutoConfigurationImportSelector:导入哪些组件的选择器;将所有需要导入的组件以全类名的方式返回;这些组件就会被添加到容器中;会给容器中导入非常多的自动配置类(xxxAutoConfiguration);就是给容器中导入这个场景需要的所有组件,并配置好这些组件;

J2EE的整体整合解决方案和自动配置都在spring-boot-autoconfigure-1.5.9.RELEASE.jar;

3.springboot 配置文件

springboot的全局配置文件有两种

  1.       application.properties
  2.       application.yml

     1. YAML语法:

      1、基本语法
            k:(空格)v:表示一对键值对(空格必须有);以空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级的

server:
  port: 8081
  path: /hello

   属性和值也是大小写敏感;

4.将配置文件中的值注入到类中

  1.     使用@ConfigurationProperties才能将配置文件中的有的属性映射到spring容器中
  2.      在JavaBean中必须配置@Component 注解将类注入到spring的容器中

      配置文件

     

person:
  lastName: hello
  age: 18
  boss: false
  birth: 2017/12/12
  maps: {k1: v1,k2: 12}
 lists:
   ‐ lisi
   ‐ zhaoliu
 dog:
   name: 小狗
   age: 12

javaBean

/**
* 将配置文件中配置的每一个属性的值,映射到这个组件中
* @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
* prefix = "person":配置文件中哪个下面的所有属性进行一一映射
*
* 只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能;
*
*/
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
   private String lastName;
   private Integer age;
   private Boolean boss;
   private Date birth;
   private Map<String,Object> maps;
   private List<Object> lists;
   private Dog dog;

可以导入配置文件对应javabean的属性提示的功能。这样的话就可以在配置文件中获取提示信息

<!‐‐导入配置文件处理器,配置文件进行绑定就会有提示‐‐>
<dependency>
   <groupId>org.springframework.boot</groupId>
      <artifactId>spring‐boot‐configuration‐processor</artifactId>
   <optional>true</optional>
</dependency>
@Value获取值和@ConfigurationProperties获取值比较
 @ConfigurationProperties@Value
功能批量注入配置文件中的属性一个个指定
松散绑定(松散语法)支持不支持
SpEL不支持支持
JSR303数据校验支持不支持
复杂数据类型封装支持不支持

总结:

如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value;
如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties;

5.配置文件注入值数据校验

   这个数据校验是对上面总结表格的实际数据

@Component
@ConfigurationProperties(prefix = "person")
@Validated
public class Person {
/**
* <bean class="Person">
* <property name="lastName" value="字面量/${key}从环境变量、配置文件中获取值/#
{SpEL}"></property>
* <bean/>
*/
//lastName必须是邮箱格式
@Email
//@Value("${person.last‐name}")
private String lastName;
//@Value("#{11*2}")
private Integer age;
//@Value("true")
private Boolean boss;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;

 

 

6.@PropertySource&@ImportResource&@Bean
  

     6.1@PropertySource:加载指定的配置文件;

是获取资源文件夹中的不是全局配置的配置文件

/**
* 将配置文件中配置的每一个属性的值,映射到这个组件中
* @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
* prefix = "person":配置文件中哪个下面的所有属性进行一一映射
*
* 只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能;
* @ConfigurationProperties(prefix = "person")默认从全局配置文件中获取值;
*
*/
@PropertySource(value = {"classpath:person.properties"})
@Component
@ConfigurationProperties(prefix = "person")
public class Person {


private String lastName;

private Integer age;

private Boolean boss;

 

6.2 @ImportResource 

主要的作用是将额外配置的xml文件注入到容器

导入Spring的配置文件,让配置文件里面的内容生效;Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别;想让Spring的配置文件生效,加载进来;@ImportResource标注在一个配置类上

@ImportResource(locations = {"classpath:beans.xml"})
导入Spring的配置文件让其生效

<?xml version="1.0" encoding="UTF‐8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring‐beans.xsd">
    <bean id="helloService" class="com.atguigu.springboot.service.HelloService"></bean>
</beans>

 

6.3 使用@Configuration 全注解的来实现加载资源文件(springboot推荐使用全注解的方式)

需要一个组件的时候,就可以使用这个方法来进行对某一个类注入到容器中

/**
* @Configuration:指明当前类是一个配置类;就是来替代之前的Spring配置文件
*
* 在配置文件中用<bean><bean/>标签添加组件
*
*/
@Configuration
public class MyAppConfig {
//将方法的返回值添加到容器中;容器中这个组件默认的id就是方法名
@Bean
public HelloService helloService02(){
     System.out.println("配置类@Bean给容器中添加组件了...");
     return new HelloService();
}
}

7.Profile

 profile 是springboot提供一个多环境切换的方式 。开发环境、测试环境、生产环境。可以改变的是对数据库连接、端口号的控制。

    7.1、多Profile文件

          我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml,默认使用application.properties的配置;

     7.2、yml支持多文档块方式

   作用是跟前面定义的多个配置文件的方式是一样的,这样的优点在于不用写很多的配置文件,就可以实现对多环境的切换。

server:
   port: 8081
spring:
  profiles:
     active: prod
‐‐‐
server:
   port: 8083
spring:
   profiles: dev
‐‐‐
server:
   port: 8084
spring:
    profiles: prod #指定属于哪个环境

7.3、激活指定profile

      激活指定profile的4种方法,目的切换不同的环境,控制端口和其他一些的资源

  1. 命令行:java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev;
  2. 在配置文件中指定: spring.profiles.active=dev
  3. 在测试的直接传入参数:spring.profiles.active=dev
  4. 虚拟机参数:-Dspring.profiles.active=dev

8.springboot配置文件加载位置

springboot在启动的过程中会加载以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文

-file:../config  config文件夹

-file:./ 

–classpath:/config/ 类路径下的config文件夹

–classpath:/ 类路径下

优先级是从高到低,高优先级的会覆盖低优先级的配置文件。springboot会从这个4个路劲加载配置文件,形成互补的方式

项目打好包后 也可以使用命令的方式来确定配置文件的位置:

java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --spring.config.location=G:/application.properties

项目打包运行后,所有的运行参数配置都可以是在命令行中输入,多个配置用空格分开; --配置项=值

比如改变访问路径:

java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.context-path=/abc

更多的命令查看官问:https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#boot-features-external-config

(体现了英文好的妙处,可惜本人英文不好。。所以要努力学习呀!!!)

9.自动配置原理

写了很多的配置文件,但是其中的奥秘还是不了解,所以凡是都要知根知底的使用起来才有底气~~~~,这就要看一看springboot的自动配置原理。

  9.1自动配置原理

  1.       springboot在启动的时候,会加载主配置类,开启了自动配置功能,@EnableAutoConfiguration
  2.       @EnableAutoConfiguration的作用
  •               利用EnableAutoConfigurationImportSelector给容器中导入一些组件
  •               可以查看selectImports()方法的内容;
  •               List configurations = getCandidateConfigurations(annotationMetadata, attributes);获取候选的配置
  •              (SpringFactoriesLoader.loadFactoryNames() 扫描所有jar包类路径下 META‐INF/spring.factories把扫描到的这些文                       件的内容包装成properties对象从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们                  添加在容器中)

        就是将 类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中;

   3.        就是这里每一个自动配置类进行自动配置功能,将配置文件中的配置信息装载在类中(这个前面说到的获取配置文件数据到实体类的方方法是一样的);

9.2 以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;

@Configuration //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@EnableConfigurationProperties(HttpEncodingProperties.class) //启动指定类的
ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把
HttpEncodingProperties加入到ioc容器中
@ConditionalOnWebApplication //Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果
满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnClass(CharacterEncodingFilter.class) //判断当前项目有没有这个类
CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing =
true) //判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
public class HttpEncodingAutoConfiguration {
//他已经和SpringBoot的配置文件映射了
private final HttpEncodingProperties properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
this.properties = properties;
}
@Bean //给容器中添加一个组件,这个组件的某些值需要从properties中获取
@ConditionalOnMissingBean(CharacterEncodingFilter.class) //判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}

spring.http.encoding 这个数据就可以在配置文件中写的前缀。

总结: 这些自动配置类会在底层进行一系列的判读是否要生成自动配置类、初始化并加载到ioc容器中。

  1. springboot在启动的时候会加载大量的自动装配类
  2. 如果在业务需求中,看有没有已经有了我们需要的自动装配类
  3. 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)
  4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值;

自动装配类是需要条件才能自动生成的,可以通过这个debug=true查看

10.日志框架的使用

     10.1,日志框架

    市面上有常见的日志框架:JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j....

   日志框架分别有日志抽象层和日志实现层,springboot底层是spring ,spring使用的日志框架是jcl。抽象层 有JCL、SLF4J、jboo-logging,日志实现:log4jJ、log4j2、logback

spring boot 底层使用的是slf4j 和logback实现

 10.2 slf4j的日志使用

  1. 如何在系统中使用SLF4j https://www.slf4j.org
  2. 以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法;给系统里面导入slf4j的jar和 logback的实现jar
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
}
}

10.3 日志使用

  1.      默认配置,只要是使用了日志记录器就可以使用
    //记录器
    Logger logger = LoggerFactory.getLogger(getClass());
    @Test
     public void contextLoads() {
     //System.out.println();
     //日志的级别;
     //由低到高 trace<debug<info<warn<error
     //可以调整输出的日志级别;日志就只会在这个级别以以后的高级别生效
     logger.trace("这是trace日志...");
     logger.debug("这是debug日志...");
     //SpringBoot默认给我们使用的是info级别的,没有指定级别的就用SpringBoot默认规定的级别;root级别
     logger.info("这是info日志...");
     logger.warn("这是warn日志...");
     logger.error("这是error日志...");
    }
    日志输出格式:
    %d表示日期时间,
    %thread表示线程名,
    %‐5level:级别从左显示5个字符宽度
    %logger{50} 表示logger名字最长50个字符,否则按照句点分割。
    %msg:日志消息,
    %n是换行符
    ‐‐>
    %d{yyyy‐MM‐dd HH:mm:ss.SSS} [%thread] %‐5level %logger{50} ‐ %msg%n

     

  2. SpringBoot修改日志的默认配置
logging.level.com.atguigu=trace
#logging.path=
# 不指定路径在当前项目下生成springboot.log日志
# 可以指定完整的路径;
#logging.file=G:/springboot.log
# 在当前磁盘的根路径下创建spring文件夹和里面的log文件夹;使用 spring.log 作为默认文件
logging.path=/spring/log
# 在控制台输出的日志的格式
logging.pattern.console=%d{yyyy‐MM‐dd} [%thread] %‐5level %logger{50} ‐ %msg%n
# 指定文件中日志输出的格式
logging.pattern.file=%d{yyyy‐MM‐dd} === [%thread] === %‐5level === %logger{50} ==== %msg%n

 

11. Web开发

  1.简介  (使用springboot)

  •  创建springboot应用,选中我们需要的模块
  • Springboot已经将这些默认的场景配置好了,我们只需要再文件中配置少量的配置就好了
  • 主要的的精力放在业务逻辑上

      提出问题:自动配置原理??

         这个springboot帮助我们配置了什么?能不能修改??能修改哪些配置???能不能扩展????    。。。。

     xxxxAutoConfiguration:帮我们给容器中自动配置组件;
     xxxxProperties:配置类来封装配置文件的内容;

   2.SpringBoot对静态资源的映射规则;

     在这个类下面可以配置查静态资源文件的路径

@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties implements ResourceLoaderAware {

 主要应用的类是

//可以设置和静态资源有关的参数,缓存时间等
ebMvcAuotConfiguration    :
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Integer cachePeriod = this.resourceProperties.getCachePeriod();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(
registry.addResourceHandler("/webjars/**")
.addResourceLocations(
"classpath:/META‐INF/resources/webjars/")
.setCachePeriod(cachePeriod));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
//静态资源文件夹映射
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(
registry.addResourceHandler(staticPathPattern)
.addResourceLocations(
this.resourceProperties.getStaticLocations())
.setCachePeriod(cachePeriod));
}
}
//配置欢迎页映射
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(
ResourceProperties resourceProperties) {
return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
}
//配置喜欢的图标
@Configuration
@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
public static class FaviconConfiguration {
private final ResourceProperties resourceProperties;
public FaviconConfiguration(ResourceProperties resourceProperties) {
this.resourceProperties = resourceProperties;
}
@Bean
public SimpleUrlHandlerMapping faviconHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
//所有 **/favicon.ico
mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
faviconRequestHandler()));
return mapping;
}
@Bean
public ResourceHttpRequestHandler faviconRequestHandler() {
ResourceHttpRequestHandler requestHandler = new
ResourceHttpRequestHandler();
requestHandler
.setLocations(this.resourceProperties.getFaviconLocations());
return requestHandler;
}
}

   1.使用webjars的方式引用静态资源

       静态资源的底层说明

  //添加静态依赖的配置
@Override
		public void addResourceHandlers(ResourceHandlerRegistry registry) {
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
			Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
			CacheControl cacheControl = this.resourceProperties.getCache()
					.getCachecontrol().toHttpCacheControl();
			if (!registry.hasMappingForPattern("/webjars/**")) {
				customizeResourceHandlerRegistration(registry
						.addResourceHandler("/webjars/**")
						.addResourceLocations("classpath:/META-INF/resources/webjars/")
						.setCachePeriod(getSeconds(cachePeriod))
						.setCacheControl(cacheControl));
			}
			String staticPathPattern = this.mvcProperties.getStaticPathPattern();
			if (!registry.hasMappingForPattern(staticPathPattern)) {
				customizeResourceHandlerRegistration(
						registry.addResourceHandler(staticPathPattern)
								.addResourceLocations(getResourceLocations(
										this.resourceProperties.getStaticLocations()))
								.setCachePeriod(getSeconds(cachePeriod))
								.setCacheControl(cacheControl));
			}
		}

 1.所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找资源;webjars:以jar包的方式引入静态资源;

访问路径:localhost:8080/webjars/jquery/3.3.1/jquery.js

<!‐‐引入jquery‐webjar‐‐>在访问的时候只需要写webjars下面资源的名称即可
<dependency>
<groupId>org.webjars</groupId>
   <artifactId>jquery</artifactId>
   <version>3.3.1</version>
</dependency>

2. "/**" 访问当前项目的任何资源,都去(静态资源的文件夹)找映射

   "classpath:/META‐INF/resources/",
   "classpath:/resources/",
   "classpath:/static/",
   "classpath:/public/"
   "/":当前项目的根路径

3. 欢迎页; 静态资源文件夹下的所有index.html页面;被"/**"映射;

localhost:8080/ 找index页面

4.所有的 **/favicon.ico 都是在静态资源文件下找

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值