第十章笔记整理Ⅱ

本文详细介绍了SpringMVC的运作流程、优势和配置,包括前端控制器DispatcherServlet、处理器映射器、处理器、处理器适配器、视图解析器等组件。此外,还探讨了SpringMVC与Servlet的关系、配置以及SpringMVC的注解使用。接着,文章简要概述了Mybatis,强调其作为持久层框架的角色,如何简化JDBC操作,以及Mybatis的配置和映射文件设置。
摘要由CSDN通过智能技术生成

SpringMVC也叫Spring web mvc是Spring内置的一个MVC框架,在Spring3.0后发布。SpringMVC框架解决了WEB开发中常见的问题(参数接收、文件上传、表单验证等),而且使用简单,与Spring无缝集成。支持RESTful风格的URL请求。采用了松散耦合可插拔组件结构,比其他MVC框架更具扩展性和灵活性。

SpringMVC是对Servlet进行深层次的封装。

SpringMVC的优势:

1.基于MVC架构,功能分工明确,解决页面代码和后台代码的分离。

2.简单易用,SpringMVC也是轻量级的,jar包很小,不依赖特定的接口和类就可以开发一个注解的SpringMVC项目。

3.作为Spring框架的一部分,能够使用IOC和AOP,方便整合MyBatis、Hiberate、JPA等其他框架。

4.SpringMVC的注解强大易用。

Model-View-Controller:模型层--视图层--控制器

模型层:javaBean负责数据访问和业务处理service dao pojo

视图层:jsp技术 负责收集和展示数据

控制器:servlet技术 中间调度

控制器的工作:

1、接受客户端的请求(包括请求中携带的数据)

2、处理请求,调用后台模型层中的业务逻辑

3、页面导航,处理完毕后给出响应的jsp页面

pom.xml文件添加依赖和插件

<packaging>war</packaging>

<dependencies>

     <!--spring-webmvc依赖-->

     <dependency>

          <groupId>org.springframework</groupId>

          <artifactId>spring-webmvc</artifactId>

          <version>5.2.13.RELEASE</version>

     </dependency>

     <!--springmvc底层还是servlet,所以必须添加servlet依赖-->

     <dependency>

          <groupId>javax.servlet</groupId>

          <artifactId>javax.servlet-api</artifactId>

          <version>4.0.1</version>        

          <scope>provided</scope><!--插件运行的时候,没有范围插件会启动失败-->

      </dependency>

</dependencies>

<build>

      <plugins>

          <!--编码、编译、JDK版本-->

          <plugin>

                <groupId>org.apache.maven.plugins</groupId>

                <artifactId>maven-compiler-plugin</artifactId>

                <version>3.8.0</version>

                <configuration>

                    <source>1.8</source>

                    <target>1.8</target>

                    <encoding>UTF-8</encoding>

                </configuration>

          </plugin>

          <!--tomcat插件-->

          <plugin>

                <groupId>org.apache.tomcat.maven</groupId>

                <artifactId>tomcat7-maven-plugin</artifactId>

                <version>2.2</version>

                <configuration>

                    <path>/</path>

                    <port>8080</port>

                </configuration>

          </plugin>

      </plugins>

</build>

创建Spring的配置文件applicationContext.xml

创建SpringMVC的配置文件springmvc.xml

在web.xml中进行Spring和SpringMVC的配置

<!--容器启动,自动加载spring的配置文件-->

<context-param>

      <param-name>contextConfigLocation</param-name>

      <param-value>classpath:applicationContext.xml</param-value>

</context-param>

<listener>

      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>

<!--springmvc的前端/核心/中央控制器-->

<servlet>

      <servlet-name>dispatcherServlet</servlet-name>

      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

      <init-param>

            <param-name>contextConfigLocation</param-name>

            <param-value>classpath:springmvc.xml</param-value>

      </init-param>

      <load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

      <servlet-name>dispatcherServlet</servlet-name>

      <url-pattern>*.do</url-pattern>

</servlet-mapping>

问:Spring容器中可以扫描所有Bean吗?

答:不可以。

当用户发送的请求达到服务端后,会寻找前端控制器DispatcherServlet去处理,而前端控制器只在SpringMVC容器中找,所以Controller必须在SpringMVC容器中扫描。

问:SpringMVC容器中可以扫描所有Bean吗?

答:可以。

但是实际开发中一般不会这么做,原因如下:

1、为了方便配置文件的管理

2、未来在Spring+SpringMVC+Mybatis组合中,要写的配置内容很多,一般都会根据功能分开编写。

(面试)

SpringMVC的工作流程

DispatcherServlet:前端(中央、核心)控制器。

用户请求的入口控制器,它就相当于mvc模式中的c,DispatcherServlet是整个流程控制的中心,相当于是SpringMVC的大脑,由它调用其它组件处理用户的请求,DispatcherServlet的存在降低了组件之间的耦合性。

SpringMVC框架提供的核心控制器需要我们在web.xml文件中配置。

HandlerMapping:处理器映射器。

HandlerMapping是派发请求的控制器,我们不需要自己控制,但它是SpringMVC运转历程中一个重要的控制器。HandlerMapping负责根据用户请求找到Handler,SpringMVC提供了不同的映射器实现不同的映射方式,例如:配置文件方式、实现接口方式、注解方式等,在实际开发中,我们常用的方式是注解方式。

Handler:处理器(即Controller)。

Handler是继承DispatcherServlet的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。由于Handler涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发Handler。

HandlerAdaptor:处理器适配器。

通过HandlerAdaptor对处理器进行执行,这是适配器模式的应用,通过扩展处理器适配器,支持更多类型的处理器,调用处理器传递参数等工作。

ViewResolver:视图解析器。

ViewResolver负责将处理结果生成View视图,ViewResolver首先根据逻辑视图名解析成物理视图名称,即具体的页面地址,再生成View视图对象,最后对View视图进行渲染,将处理结果通过页面展示给用户。SpringMVC框架提供了很多的View 视图类型,包括:jstlView、freemarkerView、pdfView等。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。

<mvc:annotation-driven/>会自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdaptor两个bean,是SpringMVC为@Controller分发请求所必须的。

@RequestMapping注解定义了处理器对于请求的映射规则,该注解可以定义在类上,也可以定义在方法上,但是含义不同。

一个@Controller所注解的类中,可以定义多个处理器方法。当然不同的处理器方法所匹配的URI是不同的,这些不同的URI被指定在注解于方法之上的@RequestMapping的value属性中。若这些请求具有相同的URI部分,则这些相同的URI,可以被抽取到注解在类之上的@RequestMapping的value属性中,此时的这个URI表示模块的名称。URI的请求是相对于Web的根目录,在类级别上的注解会将一个特定请求或请求模式映射到一个控制器之上,之后你可以另外添加方法级别的注解来进一步指定到处理方法的映射关系。

@RequestMapping的method属性用来对被注解方法所处理请求的提交方式进行限制,即只有满足method属性指定的提交方式的请求,才会执行该被注解方法。

Method属性的取值为RequestMethod枚举常量。常用的为RequestMethod.GET 与 RequestMethod.POST,分别表示提交方式的匹配规则为GET和POST提交。

url-pattern解析

在web.xml配置SpringMVC的前端控制器时有这个节点,这个节点中的值一般有两种写法:

1.在没有特殊要求的情况下,SpringMVC的前端控制器常使用后辍匹配的方式,可以写为*.do或者*.action、*.mvc等。

2.也可以写为/,但前端控制器会指向静态内容,例如.css、.js、图片等资源的获取请求时,也会当作是一个普通的Controller请求。前端控制器会调用处理器映射器为其查找相应的处理器,因此所有的静态资源获取请求均会报404错误。

案例:在index.jsp页面添加一张图片,如果节点中的值为*.do,则图片可以正常访问,如果节点中的值为/,则不能访问。

如果节点的值配置为/后,静态资源可以通过以下两种方法解决:

在springmvc的配置文件中添加以下内容:

1、<mvc:default-servlet-handler/>

2、<mvc:resources location="/images/" mapping="/images/**" />

<!--location:表示静态资源所在目录,目录不要使用/WEB-INF/及其子目录。

mapping:表示对该资源的请求,注意后面是两个星号。-->

处理器方法的参数包含以下四类:HttpServletRequest、HttpServletResponse、HttpSession以及请求中所携带的请求参数;这些参数会在系统调用时由系统自动赋值,所以我们可以在方法内直接使用。(详情看SpringMVC入门案例)

页面导航

SpringMVC有以下两种方式实现页面的转发或重定向:

1、返回字符串

return "forward转发(默认)/redirect重定向:/jsp/ok.jsp";

2、返回ModelAndView

ModelAndView mv=new ModelAndView();

mv.setViewName("forward/redirect:/jsp/ok.jsp");

添加了forward/redirect后,视图解析器中的前后缀拼接功能就失效了。

当返回值为字符串时,重定向跳转的页面无法获取到存储在request作用域的值,请求会中断;当返回值为ModelAndView时,存储在request作用域的值以参数的形式拼接在URL后面。

@ExceptionHandler可以将一个方法指定为异常处理方法。

其返回值可以是ModelAndView、String或void,方法名随意,方法参数可以是Exception及其子类对象、HttpServletRequest、HttpServletResponse等,系统会自动为这些方法参数赋值。

异常处理方法可以直接注解于@Controller中。

一般将异常处理方法专门定义在一个类中,作为全局的异常处理类。

使用@ControllerAdvice是给控制器对象增强功能的,该注解修饰的类中可以使用@ExceptionHandler。

当使用@RequestMapping修饰的方法抛出异常时,会执行@ControllerAdvice修饰的类中的异常处理方法。

@ControllerAdvice注解所在的类需要进行包扫描,否则无法创建对象。

SpringMVC中拦截器(interceptor)的作用是拦截指定的用户请求,并进行相应的预处理与后处理。

自定义拦截器,需要实现HandlerInterceptor接口,该接口中含有preHandle、postHandle、afterCompletion三个方法。

REST是指一组架构的约束条件和原则,满足这些约束条件和原则的应用程序或设计就是RESTful。

RESTful的特性:

资源(Resources):互联网所有的事物都可以被抽象为资源,URI为每一个资源独一无二的识别符,因此要获取这个资源,访问它的URI就可以。

表现层(Representation):把资源具体呈现出来的形式,比如文本可以用txt、HTML、XML等格式表现。

状态转换(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议是一个无状态协议,即所有的状态都保存在服务器端。如果客户端想要操作服务器,必须通过某种手段,让服务器端发生状态转换,而这种转换是建立在表现层之上的,因此状态转换也叫表现层状态转换。

RESTful的核心思想是客户端的用户发出的数据操作指令都是"动词+宾语"结构。

动词通常就是五种HTTP方法,对应CRUD操作。

GET:读取(Read)  POST:新建(Create)  PUT:更新(Update)

PATCH:部分更新(Update)  DELETE:删除(Delete)

PS: 1、根据HTTP规范,动词一律大写。

2、一些代理只支持POST和GET方法, 为了使用这些有限方法支持RESTful API,使用X-HTTP-Method-Override来覆盖POST方法。

宾语就是API的URL,是HTTP动词作用的对象。它应该是名词,不能是动词。

比如/expresses这个URL就是正确的。

如果资源中有多级分类,也不建议写出多级的URL。

推荐写法:GET /expresses?statu=false

HTTP状态码是一个三位数,分成五个类别,分别如下:

1xx:相关信息  //API不需要,可以直接忽略

2xx:操作成功

200 OK 表示一切正常  201 Created 表示新的资源已经成功创建

204 No Content 表示资源已经成功删除

3xx:重定向

304 Not Modified 表示客户端使用缓存数据

4xx:客户端错误

400 Bad Request 表示服务器不理解客户端的请求,未做任何处理

401 Unauthorized 表示用户未提供身份验证凭据或没有通过身份验证

403 Forbidden 表示用户通过了身份验证,但是不具有访问资源所需的权限

404 Not Found 表示所请求的资源不存在或不可用

405 Method Not Allowed 表示用户已经通过身份验证,但是所用的HTTP方法不在他的权限之内

410 Gone 表示所请求的资源已从这个地址转移,不再可用

415 Unsupported Media Type 表示客户端要求的返回格式不支持,比如API只能返回JSON格式,但是客户端要求返回XML格式

422 Unprocessable Entity 表示客户端上传的附件无法处理,导致请求失败

429 Too Many Requests 表示客户端的请求次数超过限额

5xx:服务器错误

500 Internal Server Error 表示客户端请求有效,服务器处理时发生了意外

503 Service Unavailable 表示服务器无法处理请求,一般用于网站维护状态

问题:

在Ajax中,PUT请求和DELETE请求传递参数无效,传递到后台的参数值为null。

原因:

Tomcat封装请求参数的过程:

1.将请求体中的数据,封装成一个map

2.request.getParameter(key)会从这个map中取值

3.SpringMVC封装pojo对象时,会把对象的每个属性值request.getParamter()当Ajax发送PUT请求或DELETE请求时,请求体中的数据通过request.getParamter()拿不到。因此,Tomcat检测到是PUT请求或DELETE请求就不会封装请求体中的数据为map,只有POST形式的请求才封装请求为map。

方案:

1、前端页面中的Ajax发送请求的时候,在url中加"&_method=PUT"或"&_method=DELETE"即可。

2、web.xml中配置

配置的时候多个过滤器需要注意顺序

<filter>

      <filter-name>hiddenHttpMethodFilter</filter-name>

      <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>

</filter>

<filter-mapping>

      <filter-name>hiddenHttpMethodFilter</filter-name>

      <url-pattern>*.do</url-pattern>

</filter-mapping>

Mybatis是基于java的持久层框架,它的内部封装了JDBC,让开发人员只需要关注sql语句本身,不需要花费精力在驱动加载、连接创建、statement创建等复杂的过程。

Mybatis通过XML或注解的方式,将要执行的各种的statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由Mybatis框架执行sql,并将结果直接映射为java对象。

O-object java对象   R-relation 数据库表   M-mapping 映射

采用了ORM思想解决了实体类和数据库表映射的问题。对JDBC进行了封装,屏蔽了JDBCAPI底层的访问细节,避免我们与JDBC的API打交道,就能完成对数据的持久化操作。

创建maven项目,添加Mybatis的jar依赖

<dependencies>

      <dependency>

            <groupId>org.mybatis</groupId>

            <artifactId>mybatis</artifactId>

            <version>3.5.6</version>

      </dependency>

      <dependency>

            <groupId>mysql</groupId>

            <artifactId>mysql-connector-java</artifactId>

            <version>8.0.23</version>

      </dependency>

      <dependency>

            <groupId>junit</groupId>

            <artifactId>junit</artifactId>

            <version>4.12</version>

            <scope>test</scope>

      </dependency>

</dependencies>

<build>

      <plugins>

            <plugin>

                  <artifactId>maven-compiler-plugin</artifactId>

                  <version>3.1</version>

                  <configuration>

                        <source>1.8</source>

                        <target>1.8</target>

                  </configuration>

            </plugin>

      </plugins>

</build>

Mybatis的全局配置文件(mybatis.xml)

Configuration(配置) 

            properties--属性:加载外部的配置文件,例如加载数据库的连接信息

            Settings--全局配置参数:例如日志配置

            typeAliases--类型别名

            typeHandlers----类型处理器

            objectFactory-----对象工厂

            Plugins------插件:例如分页插件

            Environments----环境集合属性对象

                        Environment(环境变量)

                        transactionManager(事务管理器)

                        dataSource(数据源)

            Mappers---映射器:注册映射文件用

全局配置文件需要在头部使用约束文件

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE configuration   

PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

            "http://mybatis.org/dtd/mybatis-3-config.dtd">

编写ORM映射文件

XML映射文件名称必须与实体类名称一致,XML映射文件必须与实体类的包路径一致(resources目录下创建与java目录下同名的包)。

将映射文件注册到mybatis.xml文件中

<configuration>

      <!--注册映射文件-->

      <mappers>

            <mapper resource="com/syh/pojo/Team.xml"/>

      </mappers>

</configuration>

在pom.xml文件中配置映射文件的扫描路径

<build>

      <resources>

            <resource>

                  <!--所在的目录-->

                  <directory>src/main/java</directory>

                  <!--包括目录下的.properties .xml文件都会扫描到-->

                  <includes>

                        <include>**/*.properties</include>

                        <include>**/*.xml</include>

                  </includes>

                  <filtering>false</filtering>

            </resource>

      </resources>

</build>

Mybatis架构

1、Mybatis.xml文件是Mybatis框架的全局配置文件,配置了Mybatis框架运行的环境等信息。

Mapper1.xml、Mapper2.xml、Mapper.xml是sql的映射文件,文件中配置了所有操作数据库的sql语句,这些文件需要在全局配置文件中加载。

2、通过Mybatis环境等配置信息构建SqlSessionFactroy ,相当于产生连接池。

3、由会话工厂创建SqlSession会话(连接),操作数据库需要通过SqlSession进行的。

4、Mybatis底层自定义了Executor执行器的接口操作数据库,Executor接口有两个实现,一个是基本的执行器,另一个是缓存的执行器。

5、Mappedstatement也是Mybatis框架一个底层的封装对象,它包装了Mybatis的配置信息以及sql的映射信息。Mapper.xml文件中的一个sql语句对应一个Mappedstatement对象,sql的id就是Mappedstatement的id。

6、Mapped statement对sql执行输入参数的定义,输入参数包括HashMap、基本类型、pojo,在执行sql语句前,Executor通过Mappedstatement将输入的java对象映射到sql语句中,执行完毕sql语句后,输出的映射就是JDBC编码中对preparedStatement执行结果的定义。

SqlSessionFactory的创建,需要使用SqlSessionFactoryBuilder对象的build()方法。事实上使用SqlSessionFactoryBuilder的原因是将SqlSessionFactory这个复杂对象的创建交由Builder来执行,也就是使用了建造者模式。

建造者模式:又称生成器模式,是一种对象的创建模式。

可以将一个产品的内部表象与产品的生成过程分割开来, 从而使一个建造过程可以生成具有不同内部表象的产品(将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示),这样用户只需指定需要建造的类型就可以得到具体产品,而不需要了解具体的建造过程和细节。

在建造者模式中,角色分指导者(Director)与建造者(Builder),用户联系指导者,指导者指挥建造者,最后得到产品。

SqlSessionFactory接口的对象是一个重量级的对象,它的线程安全,所以一个应用只需要一个该对象即可。创建SqlSession需要使用SqlSessionFactory接口的openSession()方法。

默认的openSession()方法没有参数,它会创建有以下特性的SqlSession:

1、会开启一个事务(也就是不自动提交)。

2、将从由当前环境配置的DataSource实例中获取Connection对象,事务隔离级别将会使用驱动或数据源的默认设置。

3、预处理语句不会被复用,也不会批量处理更新。

openSession(true):创建一个有自动提交功能的SqlSession

openSession(false默认):创建一个需要手动提交的SqlSession

配置日志文件

<dependency>

      <groupId>log4j</groupId>

      <artifactId>log4j</artifactId>

      <version>1.2.17</version>

</dependency>

在resource包下添加log4j.properties文件

# Global logging configuration info warning error

log4j.rootLogger=DEBUG,stdout

# Console output...

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

在mybatis.xml文件中添加日志的配置

<configuration>

      <settings>

            <setting name="logImpl" value="LOG4J"/>

            <!--是否开启二级缓存,默认false不开启,true开启-->

            <setting name="cacheEnabled" value="true"/>

      </settings>

</configuration>

ThreadLocal(线程局部变量)并非是一个线程的本地实现版本,它的功能是为每一个使用该变量的线程都提供一个变量值的副本, 是Java中一种较为特殊的线程绑定机制,使每一个线程都可以独立改变自己的副本,不会和其它线程的副本冲突(一个类似于list集合的容器,但与list集合不同点在于只能存放一个数据)。

(面试)

问:#{}和${}的区别?

答:#{}表示一个占位符,通知Mybatis使用实际的参数值代替,并使用 PrepareStatement对象执行sql语句。#{…}代替sql语句的“?”,这个是Mybatis中的首选做法,安全迅速。

${}表示字符串原样替换,通知Mybatis使用$包含的“字符串”替换所在位置,使用Statement或者PreparedStatement把sql语句和${}的内容连接起来。一般用在替换表名、列名、不同列排序等操作。

在Mybatis.xml中自定义别名

<configuration>

    <typeAliases>

        <!--对单个的实体类定义别名-->

        <typeAlias type="com.syh.pojo.Team" alias="Team"></typeAlias>

        <!--推荐写法:批量定义别名,以整个包来设置,包里所有类的别名默认为类名(首字母大小写都可以)-->

        <package name="com.syh.pojo"/>

        <package name="com.syh.mapper"/>

    </typeAliases>

</configuration>

resultType(输出映射):执行sql得到ResultSet转换的类型,使用类型的完全限定名或别名。如果返回的是集合,设置的是集合元素的类型,而不是集合本身。

注意:resultType和resultMap不能同时使用。

resultMap可以自定义sql的结果和java对象属性的映射关系,更灵活的把列值赋值给指定属性,常用于列名和java对象的属性名不一样的情况。

Mybatis数据源分为三类:UNPOOLED(不使用连接池的数据源)、POOLED(使用连接池的数据源)和JNDI(使用JNDI实现的数据源)。

前两个都实现javax.sql.DataSource接口。

缓存是一般的ORM框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。将经常查询的数据存在缓存(内存)中,用户查询该数据的时候不需要从磁盘(关系型数据库文件)上查询,而是直接从缓存中查询,提高查询效率,解决高并发问题。

MyBatis也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。

一级缓存:自动开启,SqlSession级别的缓存。

在操作数据库时需要构造sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的HashMap是互相不影响的。

一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。

当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。

Mybatis默认开启一级缓存,存在内存中(本地缓存)不能被关闭,可以调用clearCache()来清空本地缓存,或者改变缓存的作用域。

二级缓存:Mapper级别的缓存

多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace。

不同的sqlSession两次执行相同namespace下的sql语句参数相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。

Mybatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存,如果缓存中有数据就不用从数据库中获取,大大提高系统性能。

对于变化比较频繁的sql,可以禁用二级缓存。

在开始了二级缓存的xml中,对应的statement设置useCache=false禁用当前select语句的二级缓存,意味着该sql语句每次只需都去查询数据库,不会查询缓存。

useCache默认值是true。对于一些很重要的数据尽不放在二级缓存中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值