Springboot01

1.Springboot的初识

1.1.Springboot简介

Spring Boot是搭建基于Spring的工程的脚手架 。

 它并不是对Spring进行功能增强,只是对spring框架的优化,提供的是一种方便快速使用spring 的方式,能够方便快捷的搭建spring工程。

1.2.为什么要使用SpringBoot

spring的缺点

  • 配置繁琐
  • 依赖繁琐

SpringBoot的功能

  • 自动配置,约定大于配置(提供有默认配置,也支持使用xml文件方式自定义配置,也支持 properties/yml文件自定义配置,也可以使用配置类自定义配置)
  • 起步依赖:依赖传递
  • 辅助功能:tomcat内置
  • 简化了第三方框架的整合使用(提供了默认配置,直接使用就可以了)

1.3.springboot有哪些好处?

  1.  简化Spring应用开发的一个框架;
  2.  整个Spring技术栈的一个大整合
  3.  J2EE开发的一站式解决方案

2.IDEA进行Springboot环境配置maven和jdk

3.创建Springboot项目

3.1.使用springboot创建一个项目(推荐)

 如果start.spring.io连不上,可以改为http://start.aliyun.com,但是构建出来的项目会和原生的 项目不太一样,需要修改成原生项目的样子。

启动项目

 3.2.IDEA使用maven创建springboot项目

 

 

 启动项目

3.3.使用springboot搭建一个web环境

3.3.1.引入web启动器

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

3.3.2.启动类所在包下创建controller包,编写Controller类

@RestController
public class HelloController {
    @RequestMapping("/say")
    public String sayHello(){
        return "Hello Springboot!!!!";
    }
}

3.3.3.通过启动类启动springboot项目 并进行访问

修改Tomcat端口配置,在application.properties文件中


# 修改springboot默认的项目的访问端口
# 凡是能自动提示的,都是springboot默认的配置,可以通过在配置文件中自定义更改默认
配置
server.port=9001

# 修改springboot默认的项目的访问路径
server.servlet.context-path=/hello

4.Springboot注解详解

4.1.启动类注解详解

4.1.1.@SpringBootApplication注解

  • 这是一个整合的注解
@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 {
...
}

4.1.2.@Target注解:表示使用这个注解的注解在哪个地方起作用

Java的注解

  • TYPE 在Class上 interface上 enum上起作用
  • FIELD 在属性上起作用
  • METHOD 在方法上起作用
  • PARAMETER 在方法中的参数上起作用

4.1.3.@Retention注解:表示使用这个注解的注解的一个有效范围

Java的注解

  • source 编译之前
  • class 运行之前
  • runtime 运行期间也可以

4.1.4.@Documented注解

Java的注解

  • 在我们生成文档注释的时候,类上的注解也能被看到

4.1.5.@Inherited注解

Java的注解

  • 如果添加此注解,当前定义的注解就可以被继承了

4.1.6.@SpringBootConfiguration注解(关注)

  • springboot的配置注解,在其中使用了@Configuration,表明此类是个配置类。

4.1.7.@Configuration

  • 在SpringBootConfiguration注解中被使用
  • 添加上此注解的类就成为了配置类
  • 配置类就是之前在spring配置文件中写配置一样

4.1.8.@EnableAutoConfiguration注解(关注)

springboot的注解

  • 让自动配置类起作用,在其中使用了@AutoConfigurationPackage和@Import注解

4.1.9.@AutoConfigurationPackage注解 (关注)

  • 在EnableAutoConfiguration注解中被使用
  • 让相关的自动配置类的注解起作用
  • 在其中使用了@Import注解引入了Registrar.class,查找所有使用basePackages属性和 basePackageClasses属性的注解所在的类

4.1.10.@Import注解 (关注)

  • 在EnableAutoConfiguration注解中被使用
  • 引入AutoConfigurationImportSelector.class,查找所有的可用的自动配置类

4.1.11.@ComponentScan 注解 (关注)

  • 起到扫描注解的作用

4.2.自动配置类的相关注解

4.2.1.@ConditionalOnClass注解

  • 必须要有对应的类才能起作用,主要用于判断是否能自动初始化bean对象。

示例:通过@ConditionalOnClass注解判断项目中是否有Person类,有自动创建User类对象, 没有不创建。

创建User.java

public class User {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

创建配置类MyConfiguration.java,并创建获取User对象的方法,同时添加

@ConditionalOnClass注解,设置需要在项目中找到全类名为 com.test.springbootweb.bean.Person的类才能创建User对象。

@Configuration
    public class MyConfig {
        //判断条件,判断项目中是否有com.test.springbootday01anno.Person这个类,有
        就创建User对象,没有不创建
        @ConditionalOnClass(name =
                {"com.test.springbootday01anno.bean.Person"})
        @Bean
        public User user(){
            System.out.println("user()方法执行了");
            return new User();
        }
    }

TestController.java中编写访问Handler

@RestController
public class TestControler {
........
    @Autowired
    private User user;
    @RequestMapping("/test2")
    public User test2(){
        user.setName("张三");
        user.setAge(10);
        return user;
    }
}

运行测试

4.2.2.@ConditionalOnMissingBean注解

  •  判断spring容器中是否有对应的bean对象,有就不初始化,没有自动初始化改bean对象

在引导类中设置创建获取User对象的方法,因为MyConfiguration.java中已经设置了创建User对 象的方法,所以这里@ConditionalOnMissingBean,表示如果已经创建了User对象,则此方法 不在执行。

@SpringBootApplication
public class SpringbootDay01AnnoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootDay01AnnoApplication.class, args);
    }
    //判断条件,判断spring容器中是否有同名的bean对象,有不创建,没有创建
    @ConditionalOnMissingBean
    @Bean
    public User user(){
        System.out.println("引导类的user()方法执行了");
        return new User();
    }
}

4.2.3.@ConditionalOnMissingClass注解

  • 判断项目中是否有实体类,有实体类,不初始化对应的bean对象,没有实体类,初始化对 应的bean对象,和@ConditionalOnClass含义相反。

示例

将MyConfiguration.java中的@ConditionalOnClass注解换成@ConditionalOnMissingClass, 然后设置成Person会发现报错,但是随便换成没有的全类名则可以执行成功。

@Configuration
public class MyConfig {
//判断条件,判断项目中是否有com.test.springbootday01anno.bean.Person这个类,有不创建User对象,没有创建
    @ConditionalOnMissingClass({"com.test.springbootday01anno.bean.Person"})
    @Bean
    public User user(){
        System.out.println("配置类的user()方法执行了");
        return new User();
    }
}

4.2.4.@EnableConfigurationProperties注解

  • 通过此注解可以找到对应的XXXProperties配置类,加载对应默认的配置,或者修改默认的 配置。

5.Springboot的配置文件的学习

5.1.springboot的配置文件有两种写法

  • properties结尾的文件
  • .yml结尾的文件(.yaml)

如果两个文件都存在于项目中,则优先级为:properties>yml

如果properties和yml文件中配置了相同内容,以properties文件为准。

如果properties和yml文件中配置的内容不同,两个文件中的内容都会生效。

另:Springboot也支持xml文件,但是不建议使用,建议使用properties或者yml文件

5.2.application.yml配置文件的要求

  • 属性名称和值之间要用冒号进行分割,
  • 如果要写值,前面要带有一个空格,例如:属性名称: 值
  • 属性下面还有子属性回车,子属性必须tab键进行缩进(两个空格)

#属性名称和值之间要用冒号进行分割
#写值时,前面要带有一个空格
#属性下面还有子属性回车,子属性必须tab键进行缩进(两个空格)
server:
    servlet:
        context-path: /configfile

5.3.通过application.yml配置文件给Java类的属性赋值

5.3.1.编写Teacher类 注意添加注解

//在yml配置文件中配置TeachProperties中的属性的值
//并且在项目启动时加载yml中对应属性的值到TeacherProperties中保存
//加载yml配置文件的属性,赋值给实体类属性保存,要求yml中的属性名和实体类中的属性名对应
@ConfigurationProperties(prefix = "teacher") //prefix:yml文件中的属性前缀
public class TeacherProperties {
    private Integer id;
    private String name;
    private List<String> hobbys;
    private Map<String, String> maps;
    private Student stu;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public List<String> getHobbys() {
        return hobbys;
    }

    public void setHobbys(List<String> hobbys) {
        this.hobbys = hobbys;
    }

    public Map<String, String> getMaps() {
        return maps;
    }

    public void setMaps(Map<String, String> maps) {

        this.maps = maps;
    }

    public Student getStu() {
        return stu;
    }

    public void setStu(Student stu) {
        this.stu = stu;
    }
}
public class Student {
    private Integer id;
    private String name;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

5.3.2.编写yml文件

#在yml文件中自定义属性和值,并通过自定义的Java类获取属性的值保存

teacher:

        id: 1

        name: 张三

        hobbys:

                - 篮球

                - 足球

                - 乒乓球

        maps:

                - k1: 哈哈

                - k2: 嘿嘿

                  k3: 呵呵

        stu:

                id: 1

        name: 李四

 5.3.3.编写配置类

@Configuration
//@EnableConfigurationProperties({TeacherProperties.class}) //会自动构建TeacherProperties对象并存放到spring容器中
public class MyConfiguration {
    @Bean
    public TeacherProperties teacherProperties(){
        return new TeacherProperties();
    }
}

5.3.4.进行测试

@RestController
public class PropertiesController {
    @Autowired
    private TeacherProperties teacherProperties;
    @RequestMapping("/get")
    public TeacherProperties getProperties(){
        return teacherProperties;
    }
}

5.4.进行多yml文件的配置

application.yml

spring:

        profiles:

                active: test

 application-dev.yml

# tomcat端口

server:

        port: 9002

        

        teacher:

                id: 6

                name: 张三

                hobbys:

                        - 篮球

                        - 足球

                        - 乒乓球

                maps:

                        - k1: 呵呵

                        - k4: 哈哈

                stu:

                        id: 55

 application-test.yml

# tomcat端口
server:
    port: 9003

teacher:
    id: 6
    name: 王五
    hobbys:
        - 篮球
        - 足球
        - 乒乓球
    maps:
        - k1: 呵呵
        - k4: 哈哈
    stu:
        id: 55

6.日志

6.1.什么是日志

springboot为了方便快速的构建功能完整的spring项目,所以springboot自动集成了安全框架 spring-boot-starter-security、消息队列ActiveMQ、日志框架spring-boot-starter-logging 等功能性框架,让我们可以方便快捷的在spring工程中整合其他功能。

在项目开发中,都不可避免的使用到日志。没有日志虽然不会影响项目的正确运行,但是没 有日志的项目可以说是不完整的。日志在调试,错误或者异常定位,数据分析中的作用是不 言而喻的。

 6.2.市面上的日志框架

  • JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j....

 6.3.SpringBoot默认选用 SLF4j和logback

依赖关系,在spring-boot-starter-web起步依赖中的spring-boot-starter已经包含了springboot-starter-logging起步依赖,所以不用直接引入spring-boot-starter-logging起步依赖。

6.4.SLF4j使用 springboot默认级别是info-API方式

 方式一:

@RestController
public class TestController {
    private static final Logger logger = LoggerFactory.getLogger(TestController.class);
    @RequestMapping("/test1")
    public String test1() {
        logger.error("我是error");
        logger.warn("我是warning");
        logger.info("我是info");
        logger.debug("我是degu");
        logger.trace("我是trace");
        return "Hello Springboot Logging";
    }
}

默认日志级别是info,所以debug和trace级别是默认不打印。

当然我们可以在application.yml或者application.properties文件中修改springboot关于日志的 默认配置

# 修改日志级别
logging:
    level:
        com:
            test: trace
    file:
        # 指定生成的日志文件名称
        name: D:/logs/spring.log
    # 在控制台输出的日志的格式
    pattern:
        console: "%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n"
        #指定文件中日志输出的格式
        file: "%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ====%msg%n"

方式二:需要引入lombok依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
@Slf4j
@RestController
public class LoggingController {
    @RequestMapping("/test2")
    public String test1() {
        log.error("我是error");
        log.warn("我是warning");
        log.info("我是info");
        log.debug("我是degu");
        log.trace("我是trace");
        return "Hello Springboot Logging";
    }
}

会发现,我们可以直接使用日志操作,这是因为springboot已经集成了日志的相关配置,我们直 接使用默认配置即可。

查看spring-boot的依赖包内容

 一般在实际工作中,开发是使用debug或者trace级别打印日志,是为了看到更多的日志信息,方 便调试程序。部署上线时,日志级别会改为info,这样就只看警告和错误信息,debug和trace级别 日志就看不到,更容易突出问题。

6.5.指定日志的xml文件

当然如果不想在application.yml文件中进行配置,也可以使用日志的原生配置文件进行配置操作。

  • 给类路径下放上每个日志框架自己的配置文件即可;SpringBoot就不使用他默认配置的了

  •  在项目的resources目录中创建logback.xml,这时日志将不在以application.yml中的配置 为准,而是以logback.xml配置为准。
<?xml version="1.0" encoding="UTF-8"?>
<!--
scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位
是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状
态。默认值为false。
-->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
    <!-- 定义日志的根目录 -->
    <property name="LOG_HOME" value="D:/spring/log"/>
    <!-- 定义日志文件名称 -->
    <property name="appName" value="atlanou-springboot"></property>
    <!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <!--
        日志输出格式:
        %d表示日期时间,
        %thread表示线程名,
        %-5level:级别从左显示5个字符宽度
        %logger{50} 表示logger名字最长50个字符,否则按照句点分割。
        %msg:日志消息,
        %n是换行符
        -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level
                %logger{50} - %msg%n
            </pattern>
        </layout>
    </appender>
    <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其
    他文件 -->
    <appender name="appLogAppender"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 指定日志文件的名称 -->
        <file>${LOG_HOME}/${appName}.log</file>
        <!--
        当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名
        TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,
        既负责滚动也负责出发滚动。
        -->
        <rollingPolicy
                class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--
            滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
            %i:当文件大小超过maxFileSize时,按照i进行文件滚动
            -->
            <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-
                %i.log
            </fileNamePattern>
            <!--
            可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动,
            且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是,
            那些为了归档而创建的目录也会被删除。
            -->
            <MaxHistory>365</MaxHistory>
            <!--
            当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件
            滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须
            配置timeBasedFileNamingAndTriggeringPolicy
            -->
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 日志输出格式: -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ]
                [ %logger{50} : %line ] - %msg%n
            </pattern>
        </layout>
    </appender>
    <!--
    logger主要用于存放日志对象,也可以定义日志类型、级别
    name:表示匹配的logger类型前缀,也就是包的前半部分
    level:要记录的日志级别,包括 TRACE < DEBUG < INFO < WARN < ERROR
    additivity:作用在于children-logger是否使用 rootLogger配置的appender
    进行输出,
    false:表示只用当前logger的appender-ref,true:
    表示当前logger的appender-ref和rootLogger的appender-ref都有效
    -->
    <!-- hibernate logger -->
    <logger name="com.test" level="debug"/>
    <!-- Spring framework logger -->
    <logger name="org.springframework" level="debug" additivity="false">
    </logger>
    <!--
        root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个
        logger对应,
        要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个
        logger的appender和level。
    -->
    <root level="info">
        <appender-ref ref="stdout"/>
        <appender-ref ref="appLogAppender"/>
    </root>
</configuration>

7.引入数据库连接

7.1.使用默认数据源

  • 在pom.xml中引入相关依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
  • 编写连接数据库的属性
spring:
    datasource:
        driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/aa?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    # springboot内置的数据库连接池
    hikari:
        # 最小空闲连接数量
        minimum-idle: 6
  • 编写java代码进行测试
    @RestController
    public class UserController {
        @Autowired
        private DataSource dataSource;
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @RequestMapping("/select")
        public List<Map<String, Object>> getUser() {
            System.out.println(dataSource + "---------------------");
            List<Map<String, Object>> list = jdbcTemplate.queryForList("select * from user_inf");
            System.out.println(list); 
            return list;
        }
    }
    

    7.2.自定义数据源

  • springboot默认的数据源是HikariCP
  • springboot默认支持哪些数据源

spring-boot-autoconfigure.jar包 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@Configuration(
        proxyBeanMethods = false
)
@Conditional({DataSourceAutoConfiguration.PooledDataSourceCondition.class})
@ConditionalOnMissingBean({DataSource.class, XADataSource.class})
//支持的数据源Hikari.class, Tomcat.class, Dbcp2.class
//Generic.class 自定义数据源的配置
@Import({Hikari.class, Tomcat.class, Dbcp2.class, Generic.class, DataSourceJmxConfiguration.class})
protected static class PooledDataSourceConfiguration {
    protected PooledDataSourceConfiguration() {
    }
}
  • 配置druid的pom.xml配置相关依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.16</version>
</dependency>
  • com.alibaba druid-spring-boot-starter 1.1.16
    spring:
        datasource:
            driver-class-name: com.mysql.jdbc.Driver
            username: root
            password:
            url: jdbc:mysql://127.0.0.1:3306/aa?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
            # 指定数据源
            type: com.alibaba.druid.pool.DruidDataSource

    8.SpringBoot起步依赖原理分析

问题

1. 为什么Springboot项目中引入的起步依赖不需要设置版本?

2. 为什么Springboot项目中只引入了一个起步依赖,就能使用所关联的所有依赖Jar包的功能?

spring-boot-starter-parent

  • 最终使用dependencyManagement定义管理各种依赖包的版本
  • 确定了可以引入springboot相关的依赖,将项目变成springboot项目

spring-boot-starter-web

  • 本身依赖了所有spring、springmvc相关的依赖包,通过依赖传递将依赖包依赖到项目中。

9.Springboot的自动配置原理

9.1.自动配置原理

  • SpringBoot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfiguration @EnableAutoConfiguration 作用,利用EnableAutoConfigurationImportSelector给容器 中导入一些组件;

 

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

  • 每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们 来做自动配置。
  • 以RedisAutoConfiguration为例解释自动配置原理,
//标识为配置,proxyBeanMethods=false, Lite轻量级模式,@Bean 修饰的方法不会被代理。
@Configuration(
        proxyBeanMethods = false
)
//判断是否有RedisOperations类,有则创建bean对象,没有则不创建
@ConditionalOnClass({RedisOperations.class})
//加载RedisProperties,RedisProperties有redis的默认配置,也可以加载yml文件中的自定义的redis配置
@EnableConfigurationProperties({RedisProperties.class})
//引入其他配置
@Import({LettuceConnectionConfiguration.class,
        JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }
//判断spring容器中是否有名称为redisTemplate的RedisTemplate对象,没有创建,
    有不创建
    @Bean
    @ConditionalOnMissingBean(
            name = {"redisTemplate"}
    )
    public RedisTemplate<Object, Object>
    redisTemplate(RedisConnectionFactory redisConnectionFactory) throws
            UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory
                                                           redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}
  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的 properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;配置文件能配置什么就 可以参照某个功能对应的这个属性类

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值