Spring MVC

一、SpringMVC简介

1.什么是MVC?

1.1 MVC架构的定义

MVC是一种软件架构思想,将软件按照模型、视图、控制器来进行划分,各模块如下所示:

  • M-model(模型):指工程中的 model对象,作用:处理数据
  • V-view(视图):指 html\jsp等页面, 作用: 与用户交互,展示数据
  • C-controller(控制层):指工程中的 servlet, 作用:接受用户请求和响应浏览器数据

1.2 MVC架构的优势

   MVC结构开发降低了代码间的耦合性;增强了代码的复用性、可维护性、拓展性;高内聚,使开发人员更加注重业务的开发。  

1.3 MVC架构的工作原理

   用户通过浏览器发送请求到服务器,请求在服务器中被Controller接收并将请求参数封装,Controller调用相应的 Model层处理请求,Model处理完的结果返回到Controller,Controller调用相应的view视图完成数据渲染展示,最终将加结果响应给浏览器。

2.什么是SpringMVC?

2.1 SpringMVC定义

 SpringMVC底层是基于selevt实现的,并以MVC架构思想开发的一款表述层web框架。

2.2 SpringMVC特点  

  1. 与spring的IOC容器等设施无缝衔接

  2. 基于原生的Servlet,通过了功能强大的前端控制器DispatcherServlet,对请求(requst)和响应         (reponse)统一处理

  3. 表述层各细分领域需要解决的问题全方面覆盖,提供了全面解决方案

  4. 代码简洁、清晰,大幅度提升了开发效率

  5. 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可

  6. 性能卓著,适合大型互联网项目需求

2.3 SpringMVC工作流程

  1. 用户发送请求被 DispatcherServlet(前端控制器)拦截。

  2. DispatcherServlet 拦截到请求后调用 HandlerMapping(处理器映射器)

  3. HandlerMapping 找到具体的处理器(根据xml配置、注解方式查找),生成Handler(处理器)对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet

  4. DispatcherServlet 调用 HandlerAdapter(处理器适配器)

  5. HandlerAdapter 经过适配调用具体的Handler(Controller,也叫后端控制器)。

  6. Handler 执行完成返回 ModelAndView

  7. HandlerAdapter 将controller执行结果ModelAndView返回给DispatcherServlet

  8. DispatcherServlet 将 ModelAndView 传给ViewReslover(视图解析器)

  9. ViewReslover 解析后返回具体 View 给 DispatcherServlet

  10. DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)。

  11. DispatcherServlet 将渲染的视图响应给用户

2.4 SpringMVC源码执行流程

详细源码整理见:

//                                        (分析重点代码)
// DispatcherServlet对象由 Tomcat服务器创建 通过调用servlet的init方法初始化对象,只调用一次
public class DispatcherServlet extends FrameworkServlet {
    //DispatcherServlet 核心方法,处理请求的方法
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //在WEB服务器启动的时候Spring的ioc容器已经将拦截器、处理器、处理器映射器、处理器适配器都已经创建完成下述流程只是去对应存放的地方获取

        //1.根据 request请求获取 HandlerExecutionChain对象,每发送一个请求都会创建一个对象,这个对象中描述了这一次请求中都应该执行那些拦截器,顺序是怎样的,要执行的处理器是哪个
        HandlerExecutionChain mappedHandler = this.getHandler(processedRequest);

        //2.根据 处理器获取 处理器适配器(底层采用适配器模式)
        //HandlerAdapter 在web服务器启动时创建完成,多个HandlerAdapter放在一个List集合中保存
        //RequestMappingHandlerAdapter: 用于适配使用注解 @RequestMapping标注的处理器
        //SimpleServletHandlerAdapter:用于适配实现了 Controller接口的控制器
        //!!!!注意:此时和没有进行数据板绑定(表单提交的数据还没有转为bean对象)
        HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

        //3.(**内部详细代码见 拦截器模块**)
        // applyPreHandle方法执行 这里会根据拦截器的preHandle()的返回值进行判断是否执行 ,ture-执行处理器方法..  false-直接return,不向下走
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }

        //4.(**内部详细代码见 视图模块**)
        // 通过 处理器适配器 调用 处理器方法,方法执行之前进行数据绑定(底层通过 WebDataBinder 完成的),数据绑定期间会使用到 HttpMessageConverter(消息转换器)
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        //5.applyPostHandle方法执行,底层执行拦截器的 postHandle()   (**内部详细代码见 拦截器模块**)
        mappedHandler.applyPostHandle(processedRequest, response, mv);

        //6.处理分发结果(将数据响应到浏览器):页面渲染 + afterCompletion方法执行
        processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception) dispatchException);
    }

    //6.处理分发结果
    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
        //6.1 页面渲染 (**内部详细代码见 拦截器模块**)
        render(mv, request, response);
        //6.2 triggerAfterCompletion方法执行 (**内部详细代码见 拦截器模块**)
        mappedHandler.triggerAfterCompletion(request, response, (Exception) null);
    }

    //6.1 页面渲染
    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        //6.1.1 调用视图解析器,返回 view对象。
        View view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
        //6.1.2 调用视图对象的渲染方法
        view.render(mv.getModelInternal(), request, response);
    }

    //6.1.1 调用视图解析器,获取视图解析器,使用解析器解析视图
    @Nullable
    protected org.springframework.web.servlet.View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {
        //6.1.1.1 获取对应视图解析器
        ViewResolver viewResolver = (ViewResolver) var5.next();
        //6.1.1.2 使用解析器获取视图
        org.springframework.web.servlet.View view = viewResolver.resolveViewName(viewName, locale);
        //返回视图
        return view;

    }
}

//6.1.1.1 视图解析器接口
public interface ViewResolver {
    @Nullable
    org.springframework.web.servlet.View resolveViewName(String viewName, Locale locale) throws Exception;
    //视图解析器接口下有许多对应的视图解析器实现类,使用那个模板那调用那个视图解析器。例:InternalResourceViewResolve、 ThymeleafViewResolve
}

//6.1.2 视图接口
public interface View {
    //视图接口下有许多实现类,使用什么视图模板,就是用那个视图 例:InternalResourceView、ThymeleafView ...
    void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}

 二、Maven工程案例 

1.配置web.xml文件

 注册SpringMVC的 DispatcherServlet(前端控制器)

  1.1 默认配置方式

<!--配置springmvc前端控制器,对浏览器发送的请求统一进行处理-->
<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <!--    设置SpringMVC的"前端控制器"所能处理的请求和请求路径
      / 表示,除xx.jsp结尾的请求路径之外的所有请求路径
      /* 表示,所有的请求路径,如果是xx.jsp路径,那么就走jsp对应的servlet,不走springmvc的前端控制器 -->
    <url-pattern>/</url-pattern>
</servlet-mapping>

   此配置作用下, SpringMVC的配置文件默认位于"/WEB-INF"下,文件名为springmvc-servlet.xml

  1.2 拓展配置方式 - (优先选择的配置方式)

<!--配置springmvc前端控制器,对浏览器发送的请求统一进行处理-->
<servlet>
    <servlet-name>springMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--拓展配置的添加位置-->
    <!--拓展1:通过初始化参数指定SpringMVC配置文件的位置和名称-->
    <init-param>
        <!--contextConfigLocation为固定值-->
        <param-name>contextConfigLocation</param-name>
        <!--classpath: 标识从类路径查找配置文件,例-maven工程的src\main\resources
            注意:刚创建时会爆红,等在resources文件下创建同名配置文件就解决了
        -->
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <!--拓展2:
        作为框架的核心组件,在启动过程中有大量的初始化操作要做,而这些操作放在的第一次
        请求时才执行会严重影响访问速度
        所以才将前端控制器DispatcherServlet的初始化时间提前到服务器启动时
    -->
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <!--   设置SpringMVC的"前端控制器"所能处理的请求和请求路径 
     / 表示,除xx.jsp结尾的请求路径之外的所有请求路径 
     /* 表示,所有的请求路径,如果是xx.jsp路径,那么就走jsp对应的servlet,不走springmvc的前端控制器 -->
    <url-pattern>/</url-pattern>
</servlet-mapping>

   可通过<init-param>标签设置SpringMVC配置文件的位置和名称,通过<load-on-startup>标签设置SpringMVC的DispaterServlet(前端控制器)的初始化时间。

2.创建请求控制器

    由于前端控制器对浏览器发送的请求进行了统一的处理,但是具体的请求有不同的处理过程,一次需要创建处理具体请求的类,即-请求控制器(controller类),类中的方法称为控制器方法

    SpringMVC的控制器由一个entity担任,因此需要@Controller注解将其标识为一个控制层组件,交给Spring的IOC容器管理,此时SpringMVC才能够识别控制器的存在

@Controller
public class HelloController {
 // 如果controller层需要通过thymeleaf去访问页面,注解必须为@Controller,而不是@RestConreoller
 // @RestController相当于@Controller和@ResponseBody合在一起的作用;
 // 如果使用@RestController注解Controller层的话,则返回的是return里面的内容,无法返回到指定的页面,配置的视图解析器InternalResourceViewResolver也就自然没有作用了;
 // 如果要返回到指定的页面,则需要用@Controller配合视图解析器InternalResourceViewResolver;
 // 如果需要返回JSON、XML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。
}

3.创建springMVC.xml

<?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">
    <!--Spring MVC框架配置文件-->

    <!--扫描组件: 自动扫描base-package路径下被注解@Controller的类-->
    <context:component-scan base-package="com.kk.controller"></context:component-scan>

    <!--配置视图解析器 Thymeleaf为例-->
    <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <!--作用于视图渲染的过程,设置视图渲染后的编码格式-->
        <property name="characterEncoding" value="UTF-8"/>
        <!--如果配置多个视图解析器,它来决定视图解析器的使用顺序,数值越小,优先级越高-->
        <property name="order" value="1"/>
        <!--当-->
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
                        <!--设置模板文件位置(前缀)-->
                        <property name="prefix" value="/WEB-INF/templates"/>
                        <!--设置模板文件后缀-->
                        <property name="suffix" value=".html"/>
                        <!--设置模板类型-->
                        <property name="templateMode" value="HTML5"/>
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

    <!--处理静态资源,例如html、js、css、jpg
        若只设置该标签,则只能访问静态资源,其他请求则无法访问
        此时必须设置<mvc:annotation-driven/>标签来解决问题-->
    <!--<mvc:default-servlet-handler/>-->

    <!--开启mvc注解驱动-->
    <!--<mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
               <property name="defaultCharset" value="utf-8"/>
                <property name="supportedMediaTypes">
                    <list>
                        <value>/html</value>
                        <value>application/json</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>-->

</beans>

三、@RequestMapping注解

   @RequestMapping注解可以标注在类上也可以标注在方法上,它的作用是将请求和处理请求的控制器方法进行关联,建立映射关系

@AliasFor("path")             //value = path 两者相同
String[] value() default {};  //value属性底层结构
@Controller   //标志这个类被springIOC进行管理
@RequestMapping("/hello")   
public class HelloController {

    @RequestMapping("/world")  
    public String helloWorld(){
        return "hello world";
    }

}

1.@RequestMapping注解的value属性

1.1  value属性的使用

value属性是一个必须设置的属性,通过请求的请求地址匹配请求映射,它可以是一个字符串,也可以是一个字符串类型的数组(代表此方法可以映射多条请求路径)。

@RequestMapping("/world")   //如果注解中只有value属性可以简写
//@RequestMapping(value="/world") 
public String helloWorld1(){
    return "hello world1";
}

@RequestMapping(value = {"/world","/xx"})  //数组的写法
public String helloWorld2(){
    return "hello world2";
}

1.2 Ant风格的value

@RequestMapping中的value支持Ant风格,支持模糊匹配路径的规则:

   ? 表示任意一个字符。(除 / ?之外的其他字符),切记一定是一个字符,不能空着。

   *   表示0~n个任意字符。(除 / ?之外的其他字符)。

   **  表示0~n个任意字符,并且路径中可以出现 / 。但是 ** 在使用使左边只可以是 /。

@Slf4j
@RestController
public class ValueController {

    //@RequestMapping(value = "/mvc/?test")
    //@RequestMapping(value = "/mvc/te*st")
    @RequestMapping(value = "/mvc/**")
    public String valueAnt(){
        //注意:
        //   如果使用Spring5及其之前的版本,这样写没问题:@RequestMapping(value = "/**/mvc")
        //   如果使用Spring6及其之后的版本,这样写会报错:@RequestMapping(value = "/**/mvc"),需要这样写:@RequestMapping(value = "/mvc/**")
        return "ok";
    }

}

1.3 value中的占位符

传统请求路径: http://localhost:33921/val?name=te​​​​​​st&password=123

RESTful风格:http://localhost:33921/val/test/123

RESTful将类路径中请求参数名称进行省略,后端通过 {}和@PathVariable来映射请求路径中的参数。

@RequestMapping("val/{name}/{password}")
    public String restPath(@PathVariable("name") String name, @PathVariable("password")String password){
        return "name = " + name + ",password = " + password ;
    }

2. @RequestMapping注解的method属性

2.1 method属性的作用

method属性限制了请求方式,分别是get/post/put/delete请求,只有发送的请求方式一样才可以成功先获取数据,method属性也可以是多个值,以数组形式书写。

RequestMethod[] method() default {}; //method属性底层结构
    @RequestMapping(value="/world",method = RequestMethod.GET)    //get请求     派生注解 @GetMapping
    @RequestMapping(value="/world",method = RequestMethod.POST)   //post请求    派生注解 @PostMapping
    @RequestMapping(value="/world",method = RequestMethod.PUT)    //put请求     派生注解 @PutMapping
    @RequestMapping(value="/world",method = RequestMethod.DELETE) //delete请求  派生注解 @DeleteMapping
  但是目前浏览器只支持get和post,若在form表单提交时,为method设置了其他请求方式的字符串(put或delete),则按照默认的get请求方式处理

2.2 web请求方式

前端向服务器发送请求的方式共有九种,前五种常用。

GET:     获取资源,只读取数据,不影响数据状态和功能。在HTTP请求头或URL中传递。
POST:    提交资源,可能还会改变数据状态和功能。通过表单等方式提交请求体。
PUT:     更新资源,更新指定资源的数据内容。通过HTTP请求体发送更新内容。
DELETE:  删除资源,删除指定资源。删除的资源表示符放在URL中或请求体中。
HEAD:    请求服务器返回资源的头部,与GET相似,但是返回的是头部信息,不包含数据体。主要用于资源检测和缓存控制。
PATCH:   部分更改请求。当被请求的资源是可被更改的资源时,服务器对该资源进行部分更新。
OPTIONS: 请求获取服务器支持的请求方法类型,以及支持的请求头标志。"OPTIONS *" 则返回支持的全部方法。
TRACE:   服务器响应输出客户端的HTTP请求,主要用于调试和测试。
CONNECT:  建立网络连接,通常用于SSL/TLS连接。

注意:

  使用超链接或原生form表单只能提交GET和POST请求,PUT/DELETE/HEAD使用ajax发请求。

  使用超链接发送或者使用form表单,没有设置method属性,发送的是GET请求。

  使用form表单,设置method = "get",发送的是GET请求。

  使用form表单,设置method = "post",发送的是POST请求。

  使用form表单,设置method = "put/delete/head",发送的是GET请求。

3. @RequestMapping注解的params属性

@RequestMapping注解的params属性的是一个数组结构,它可以对请求进行一个简单的验证,只有符合条件的请求才可以获取到数据,不符合条件的会出行400错误(请求参数不一致)。

String[] params() default {};  //params属性的底层数据结构

下面简单列举params属性的四种用法

// params ="param":要求请求映射所匹配的请求必须携带param=value 请求参数
@RequestMapping(value = "/hello",method = RequestMethod.GET,params = "id")   //请求必须携带id字段
// params = "!param":要求请求映射所匹配的请求必须不能携带param请求参数
@RequestMapping(value = "/hello",method = RequestMethod.GET, params = "!id") //请求不能携带id字段
//"param!=value":要求请求映射所匹配的请求必须携带param请求参数但是param!=value
@RequestMapping(value = "/hello",method = RequestMethod.GET,params = "id!=1")  //请求必须携带id字段,但是值不能为1
// params = "param=value":要求请求映射所匹配的请求必须携带param请求参数且param=value
 @RequestMapping(value = "/hello",method = RequestMethod.GET, params = "id=1") //请求必须携带id字段,且值等于1

4. @RequestMapping注解的headers属性(了解) 

    headers属性通过请求的请求头信息匹配请求映射, 是一个字符串类型的数组,可以通过四种表达式设置请求头信 息和请求映射的匹配关系。
String[] headers() default {}; 
  1. headers = "header":要求请求映射所匹配的请求必须携带header请求头信息
  2. headers = "!header":要求请求映射所匹配的请求必须不能携带header请求头信息
  3. headers = "header = value":要求请求映射所匹配的请求必须携带header请求头信息且header = value
  4. headers = "header != value":要求请求映射所匹配的请求必须携带header请求头信息且header != value
    若当前请求满足 @RequestMapping 注解的 value method 属性,但是不满足 headers 属性,此时页面 显示404 错误,即资源未找到!

四、SpringMVC获取参数方式

1. 使用原生Servlet API获取

  将HttpServletRequset作为控制器方法的形参,此时HttpServletRequset类型的参数标识封装了当前请求报文的对象。启动时SpringMVC会自动将Tomcat创建的HttpServletRequset对像传递给处理器。

@RequestMapping("/getName")
public String hello1(HttpServletRequest http){
    String name =http.getParameter("name");
    return "我的名字叫做"+name;
}

 2. 依靠控制器形参获取

   在控制器方法中的形参位置,设置和请求参数同名的形参,当浏览器发起请求,匹配请求映射,在DispatcherServlet中就会请求参数赋值相应的形参。

@RequestMapping("/getAddress")
public String address(String name,String address){
    return "我的名字叫"+name+",家住在"+address;
}

注意:
  • 若请求所传输的请求参数中有多个同名的请求参数,此时可以在控制器方法的形参中设置字符串、数组或者字符串类型的形参接收此请求参数
  • 若使用字符串数组类型的形参,此参数的数组中包含了每一个数据
  •  若使用字符串类型的形参,此参数的值为每个数据中间使用逗号拼接的结果

3. 使用POJO类接收参数

  在控制器方法的形参位置使用实体类来接收参数,此时浏览器传输的请求参数名和实体类中的属性名一致,那么请求参数就会为此属性赋值。

@RequestMapping("/entity")
public String entity(User user){
    return "我的名字叫做:"+user.getName();
}

4. 使用@RequestParam获取

作用: 是将请求参数和控制器方法的形参创建映射关系

@RequestParam的三个属性

value :指定为形参赋值的请求参数的参数名
required :设置是否必须传输此请求参数,默认值为 true
  • true时,则当前请求必须传输value所指定的请求参数,若没有传输该请求参数,且没有设置defaultValue属性,则页面报错400Required String parameter 'xxx' is not present
  • 为 false,则当前请求不是必须传输value所指定的请求参数,若没有传输,则注解所标识的形参的值为 null
defaultValue:不管 required 属性值为 true false ,当 value 所指定的请求参数没有传输或传输的值 为"" 时,则使用默认值为形参赋值
@RequestMapping("/requestParam")
public String requestParam(@RequestParam(value = "user_name",required = false,defaultValue = "kk") String username){
    return "我的名字叫做:"+username;
}

5. 使用@RequestHeader获取

作用: 将请求头信息和控制器方法的形参创建映射关系

@ RequestHeader 注解同@RequestParam一样,都有 value required defaultValue三个属性
@RequestMapping("/requestHeader")
public String requestHeader(@RequestHeader(value = "Host",required = false) String host){
    return "我的端口号:"+host;
}

6. 使用@CookieValue获取

作用: cookie数据和控制器方法的形参创建映射关系

@CookieValue注解同@RequestParam一样,都有valuerequireddefaultValue三个属性

@RequestMapping("/cookieValue")
public String cookieValue(@CookieValue("JSESSIONID") String JSESSIONID){
    return "JSESSIONID:"+JSESSIONID;
}
//注意: 这里要先在请求中创建session后才能进行访问

拓展

                                          解决获取请求参数乱码的问题

   get请求参数发生乱码,找到tomcat的conf/server.xml文件中在<connector>标签中添加URIEncoding="UTF-8"  添加上就可以解决get请求乱码问题

   post请求参数发生乱码,需要在web.xml文件中添加编码过滤器(CharacterEncodingFilter),注意SpringMVC中处理编码的过滤器一定要配置到其他过滤器之前,否则无效。

<!--配置springMVC的编码过滤器-->
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--设置请求参数格式-->
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
<!--设置响应参数格式-->
    <init-param>
        <param-name>forceResponseEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

五、Spring MVC 域对象

Spring MVC - 域对象_springmvc域对象-CSDN博客

六、Spring MVC 视图

Spring MVC - 视图-CSDN博客

七、Spring MVC 消息转换器

https://blog.csdn.net/weixin_58668484/article/details/137834710

八、Spring MVC 异常处理器

Spring MVC - 异常处理器-CSDN博客

九、Spring MVC 拦截器

Spring MVC - 拦截器-CSDN博客

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值