String MVC

一、Front设计模式

(1)概念:

Front(前端)设计模式就是有一个前端(不是前端专业那个前端,是最前面的意思)统一入口,在统一入口根据请求url调用自己的编写的普通方法。其中这个入口称为DispatcherServlet(Servlet分发器)。

(2)优点

  • 只需要在一个Servlet中编写获取容器Bean的代码,减少了代码冗余。

  • 不需要为每个控制器都创建一个类,而是可以在一个普通Java类中提供普通实例方法代表以前servlet中的services方法。

  • 因为可以自己编写普通Java类,这类可以放入到Spring容器中,注入Service更方便

  • 同时因为是自己编写的Java,所以可以进行一些封装,对其他操作进行简化。(代码中没有体现)

二、SpringMVC 环境搭建

(1)引入Spring-webmvc依赖

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.3.23</version>
</dependency>

(2)创建Spring MVC配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 扫描控制器类,千万不要把service等扫描进来,也千万不要在Spring配置文件扫描控制器类所在包 -->
    <context:component-scan base-package="com.bjsxt.controller"></context:component-scan>
    <!-- 让Spring MVC的注解生效-->
    <mvc:annotation-driven></mvc:annotation-driven>
    <!-- 静态资源放行
         mapping:表示URL什么样的路径放行
         location:去哪找资源
     -->
    <mvc:resources mapping="/images/**" location="/images/"/>
</beans>

(3)编写web.xml,让DispatcherServlet生效

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <!-- 参数名称必须叫做:contextConfigLocation。单词和大小写错误都导致配置文件无法正确加载 -->
            <param-name>contextConfigLocation</param-name>
			<!-- springmvc.xml 名称自定义,只要和后面创建的文件名称对应就可以了。 -->
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <!-- Tomcat启动立即加载Servlet,而不是等到访问Servlet才去实例化DispatcherServlet -->
        <!-- 配置上的效果:Tomcat启动立即加载Spring MVC框架的配置文件-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!-- /表示除了.jsp结尾的uri,其他的uri都会触发DispatcherServlet。此处前往不要写成 /* -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>


    <!--配置字符编码过滤器-->
    <filter>
        <filter-name>code</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>
    </filter>
    <filter-mapping>
        <filter-name>code</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

(4)创建一个类放入到Spring MVC容器中

package com.bjsxt.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller// 放入到Spring MVC容器中
public class FirstController {
    /*
     * 官方标准写法:
     *      返回值是ModelAndView,对象中存储跳转资源路径及作用域值
     */
    // 当前方法的映射路径
    @RequestMapping("/first")
    public ModelAndView test1(){
        ModelAndView modelAndView = new ModelAndView("first.jsp");
        return modelAndView;
    }
    /*
     * 简化写法(平时使用的方式)
     *      返回值是String,表示跳转的资源路径
     */
    @RequestMapping("/first2")
    public String test2(){
        return "first.jsp";
    }
}

三、@RequestMapping

(1)value"" 默认属性,配置映射路径,可以配置多个

(2)path="" 配置映射路径,可以配置多个

(3)name="" 备注

(4)method={RequestMethod.} 控制只能某种请求方法访问这个方法(控制单元),不配置默认什么请求方法都可以,如果不支持该请求方式报405状态码

(5)params="" 请求时必须包含某个参数,如果没有报400状态码

(6)consumes 设置请求体Content-Type类型,只能结合@RequestBody使用

(7)produces 设置响应体Content-Type类型,只能结合@ResponseBody使用

四、转发和重定向

(1)转发

在返回值字符串之前拼接 forward: 

(2)重定向

在返回值字符串之前拼接 redirect:

五、作用域传值

(1)和原生servlet的api多了一个Model传值,实际还是request作用域传值。

(2)还有一种方式可以直接使用Map传值,实际上还是request作用域。

六、获取请求参数

(1)可以直接在参数列表中通过name名直接获取

(2)@RequestParam 设置请求参数的注解

        1.name和value要接收请求参数的哪个参数

        2.defaultValue:默认值。表示当请求参数中没有这个参数时给与的默认值

        3.required:boolean类型,表示请求中是否必须包含参数

(3)接收一个自定义类型的参数,类中的属性名和请求参数名对应,默认通过set方法设置

(4)@DateTimeFormat接收日期类型参数,该注解也可以放在属性上

        pattern属性设置格式:@DateTimeFormat(pattern="yyyy-MM-dd")

(5)当请求参数为多个同名参数时,可以通过数组接收。如果向使用集合接收,必须有@RequestParam("参数名")

(6)使用路径传参(restful)

        请求路径写成

                /bjsxt/test/chen/123/23

        映射写为

                /test/{name}/{password}/{age}

        参数列表一定要加@PathVariable("name"),如果名字相同可以不写参数

                (@PathVariable("name") String name,@PathVariable("name") String password,@PathVariable("name") int age)

七、视图解析器

解决资源路径过于复杂的问题。

实际方式:做字符串拼接,为每个资源路径都加前缀和后缀。

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!-- 前缀 -->
    <property name="prefix" value="/WEB-INF/page/"/>
    <!-- 后缀 -->
    <property name="suffix" value=".jsp"/>
</bean>

八、文件上传

(1)在Html的form中enctype属性控制请求体数据类型

        application/x-www-form-urlencoded :默认值,表示普通表单数据,传递的都是字符串参数

        multipart/form-data:如果表单中除了普通字符串参数以外,还包含文件流数据,必须设置成这个

        text/plain:大文本数据。传递的内容是比较大的字符串。常用在邮箱。

(2)导入commons-fileupload依赖

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

(3)在springmvc容器中配置

<!-- 文件上传时,必须配置文件解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 设置文件最大大小,单位字节 -->
    <property name="maxUploadSize" value="1024"/>
</bean>

(4)使用及方法

@Controller
public class PeopleController {
    /**
     * 文件上传控制单元方法实现
     *
     * @param name    也可以使用JavaBean接收name的值
     * @param address 也可以使用JavaBean接收address的值
     * @param photo   名字必须和表单中文件域的name属性值相同
     * @return
     * @throws IOException transferTo抛出的异常,可以使用try...catch处理异常。示例中为了让代码看起来简洁直接抛出了。
     */
    @RequestMapping("/upload")
    public String upload(String name, String address, MultipartFile photo) throws IOException {
        photo.transferTo(new File("D:/images", photo.getOriginalFilename()));
        return "/upload.jsp";
    }
}

 (5)生成唯一文件名的方式

        时间戳+随机数

        UUID(jdk自带)

    /**
     * 文件上传控制单元方法实现
     * @param name 也可以使用JavaBean接收name的值
     * @param address 也可以使用JavaBean接收address的值
     * @param photo 名字必须和表单中文件域的name属性值相同
     * @return
     * @throws IOException transferTo抛出的异常,可以使用try...catch处理异常。示例中为了让代码看起来简洁直接抛出了。
     */
    @RequestMapping("/upload")
    public String upload(String name, String address, MultipartFile photo) throws IOException {
        // 判断上传文件流是否为空。如果不为空继续执行
        if(!photo.isEmpty()) {
            // 获取项目部署后的文件绝对路径
            // String realPath = req.getServletContext().getRealPath("/page");

            // 使用UUID生成文件名称
            // String fileName = UUID.randomUUID().toString();
            // 使用时间戳+随机数生成文件名
            long timeMillis = System.currentTimeMillis();
            Random random = new Random();
            String fileName = timeMillis + "" + random.nextInt(1000);
            // 获取上传时文件名
            String oldName = photo.getOriginalFilename();
            // 获取上传时文件的扩展名
            String suffix = oldName.substring(oldName.lastIndexOf("."));
            // 保存文件到D:/images中。必须保存D盘下已经存在images文件夹
            photo.transferTo(new File("D:/images",fileName + suffix));
        }
        return "/upload.jsp";
    }

 (6)字节输出流

    // 方法返回值为void
	@RequestMapping("/uploadfile/{filepath}")
    public void showImage(@PathVariable String filepath, HttpServletResponse resp) throws IOException {
        FileInputStream is = new FileInputStream("D:/images/" + filepath);
        // Resonse对象getOutputStream()获取响应流。
        ServletOutputStream os = resp.getOutputStream();
        // Commons-io.jar中的工具类。
        // copy(InputStream,OutputStream)表示表InputStream中内容拷贝到OutputStream中
        IOUtils.copy(is, os);
    }

九、文件下载

 (1)在Http协议中,响应头参数Content-Disposition参数可取值有两个:

        inline:默认值。表示浏览器能解析就显示,不能解析就下载。

        attachment。以附件形式下载(恒下载)

    @RequestMapping("/download")
    public void download(HttpServletRequest req, HttpServletResponse response, String filename) {
        try {
            // filename=的值就是客户端看到的下载文件名称
            response.setHeader("Content-Disposition", "attachment;filename=" + filename);
            File file = new File(req.getServletContext().getRealPath("/images"), filename);
            FileInputStream fis = new FileInputStream(file);
            ServletOutputStream os = response.getOutputStream();
            IOUtils.copy(fis, os);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

十、@ResponseBody

(1)放在方法上,可以把方法的返回值转换为json字符串,并设置到响应体中。

注意:会使得方法没有跳转功能。

(2)想要改变@ResonseBody注解的响应内容类型(Content-Type)只能通过@RequestMapping的produces属性进行设置。如果返回值是String类型并且返回值中有中文必须这样设置。

    @RequestMapping(value="/demo1",produces = "text/html;charset=utf-8")
    @ResponseBody
    public String demo1() {
        return "幽默涵养";
    }

(3)@RestController写在类上,表示该类上所有方法都加了@ResponseBody

十一、@RequestBody

服务端接收请求体中包含JSON字符串的请求时,需要在参数前面添加@RequestBody。表示使用Jackson把请求体中JSON/XML格式的数据转换为JavaBean或Map

    @RequestMapping("/testContentType")
    @ResponseBody
    public People testContentType(@RequestBody People peo) {
        System.out.println(peo);
        return peo;
    }

十二、拦截器

(1)定义:Spring MVC中提供的一个类似filter(过滤器)的功能。

 (2)使用

        1.创建java类实现HandlerInterceptor接口,重写三个方法

        参数:

                Object handler:HandlerMethod类型,存储了拦截的单元方法的method对象。

                ModelAndView: 存储了model和view信息的对象。

                Exception:存储异常信息的对象,如果没有异常信息则默认为null。

package com.bjsxt.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

        2.在springmvc.xml中配置拦截器拦截哪些控制单元

<mvc:interceptors>
    <!-- 为所有控制单元配置拦截器 -->
    <bean class="com.bjsxt.interceptor.MyInterceptor"/>
    <mvc:interceptor>
        <!-- 为所有控制单元配置拦截器 -->
        <mvc:mapping path="/**"/>
        <!-- 不为某个控制单元配置拦截器 -->
        <mvc:exclude-mapping path="/test"/>
        <bean class="com.bjsxt.interceptor.MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

(3)拦截器栈

拦截器栈指多个拦截器。当一个控制单元被多个拦截器拦截时,就形成了拦截器栈。拦截器栈中拦截器有着严格的执行顺序。执行顺序按照配置顺序执行。先配置的优先级更高,执行完控制单元,是先配置的后执行。

十三、Spring MVC异常处理

(1)局部配置

每个控制器类中可以有多个处理异常的方法。每个方法上面只需要有@ExceptionHandler,千万别添加了@RequestMapping注解。参数:value设置出现什么异常类型。

注意:只在当前类生效。

@Controller
public class DemoController {
    @RequestMapping("/demo")
    @ResponseBody
    public String demo2(){
        Object obj = null;
        obj.toString();
        return "demo2";
    }

    @ExceptionHandler(value = ArithmeticException.class)
    public String myexception(){
        System.out.println("Demo-1");
        return "forward:/exception.jsp";
    }

    @ExceptionHandler(value = Exception.class)
    public String myexception2(){
        System.out.println("Demo-2");
        return "forward:/exception2.jsp";
    }
}

(2)全局配置

创建一个类在类上加@ControllerAdvice注解,在该类中的异常处理方法就会在所有控制类中生效。

@ControllerAdvice
public class MyExceptionController {
    @ExceptionHandler(value = ArithmeticException.class)
    public String myexception(){
        System.out.println("MyException-1");
        return "forward:/exception.jsp";
    }
    @ExceptionHandler(value = Exception.class)
    public String myexception2(){
        System.out.println("MyException-2");
        return "forward:/exception2.jsp";
    }
}

(3)使用配置文件配置

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>    
                <prop key="java.lang.NullPointerException">/error1.jsp</prop>
                <prop key="java.lang.Exception">/error2.jsp</prop>
            </props>
        </property>
    </bean>

(4)在web.xml中配置

上面的方式只有在Spring MVC中出现异常时才会触发,也可以使用Java EE中的配置方式。在web.xml中配置error-page即可。

这种在web.xml配置的方式是针对整个项目出现的异常。而在springmvc.xml配置文件的配置方式只是针对Spring MVC框架出现的异常。

    <error-page>

        <!-- 根据异常类型控制 -->
        <exception-type>java.lang.NullPointerException</exception-type>

        <!-- 根据状态码控制 -->

        <error-code>500</error-code>
        <location>/error3.jsp</location>
    </error-page>

十四、数据校验

注解含义
@AssertFalse类型必须是布尔,取值必须为false
@AssertTrue类型必须是布尔,取值必须为true
@DecimalMax("3")最大值为3,value属性是String类型。
@DecimalMin("1")最小值为1,value属性是String类型。
@Digits(integer = 10,fraction = 3)integer:整数位最大长度,fraction小数位最大长度
@Email必须是邮箱地址。只要中间包含@,且@前后都具有超过1位的字符就能通过校验。字符可以是数字字母下划线
@Future类型必须是时间类型,允许为null,如果设置值必须是一个将来的时间
@FutureOrPresent类型必须是时间类型,允许为null,如果设置值必须是一个一个将来或现在的时间(精确到秒)
@Max(5)最大值为5,value属性是long类型
@Min(1)最小值为1,value属性是long类型。
@Negative必须是负数,对数据类型没有要求。
@NegativeOrZero必须是负数或零,对数据类型没有要求。
@NotBlank用在String类型。不能是空白(null和"")
@NotEmpty用在String类型。不能是空白(null和"")
@NotNull不能为null,可以是""。可以用在所有类型中。对于八大基本数据类型来说,永远不为null。
@Null必须为Null。可以用在所有类型中。对于八大基本数据类型来说,永远不可能为null。
@Past类型必须是时间类型,必须是一个过去的时间。精确到秒。
@PastOrPresent类型必须是时间类型,必须是一个过去的时间或现在的时间。精确到秒。·
@Pattern(regexp = "\w{1,6}")必须满足正则表达式。regexp是必有属性。
@Positive必须是正数,对数据类型没有要求。
@PositiveOrZero必须是正数或零,对数据类型没有要求。
@Size(min = 1,max = 10)用在String类型。个数必须在1和10之间
注解含义
@Length(min = 1,max = 10)用在String类型。长度需要在1和10之间
@Range(min = 1,max = 10)数据类型没有要求。取值范围需要在1和10之间
@URL(port = 8080,host = "127.0.0.1",protocol = "https")需要是一个合法的URL。默认情况下只要是以http:开头即可。可以通过port限制端口、host限制主机名、protocol限制协议

(1)导入hibernate-validator依赖

        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.1.7.Final</version>
        </dependency>

(2)在实体类属性上添加注解

public class People {
    @NotNull(message = "姓名不能是null")
    @Length(min = 2,max = 6,message = "长度应该是2-6位")
    private String name;
    private String age;
    // 省略Getter和Setter
}

(3)控制单元中添加@Valid注解,否则校验不生效

    @RequestMapping("/test1")
    @ResponseBody
    public People test1(@Valid People people,BindingResult result) {
        // 如果有异常信息
        if (result.hasErrors()) {
            // 获取异常信息对象
            List<ObjectError> errors = result.getAllErrors();
            // 将异常信息输出
            for (ObjectError error : errors) {
                System.out.println(error.getDefaultMessage());
            }
        }
        return people;
    }

十五、跨域

(1)跨域介绍

跨域:当前项目的协议、ip、端口和访问的URL的协议、IP、端口中有一个不同,这种访问就叫跨域。

跨域只发生在Ajax请求中。

Ajax研发之初为了保证安全性,设置默认情况下不允许跨域访问。

 (2)实现跨域原理

只要在控制单元方法上添加了@CrossOrigin注解后,会在响应头中添加Access-Control-Allow-Origin:*

Access-Control-Allow-Origin是HTTP协议中允许哪些IP的项目跨域访问,*表示所有IP

十六、国际化

(1)新建配置文件:属性文件语法:任意名_语言_国家.properties。例如:中文是zh、英文是en。如果为了更加精确是哪国使用的这个语言,可以在后面添加国家,因为美式英语和英式英语是不一样的。中国:CN、美国是US,国家缩写都是大写的。

新建suiyi_zh_CN.properties

bjsxt.username=用户名
bjsxt.password=密码
bjsxt.submit=登录

新建suiyi_en_US.properties

bjsxt.username=username
bjsxt.password=password
bjsxt.submit=login

(2)在springmvc.xml中添加额外配置。

    <!-- 加载属性文件 -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="suiyi"></property>
    </bean>
    <!-- 默认也是AcceptHeaderLocaleResolver,所以可以不配置-->
    <bean id="localeResovler" class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver"></bean>

(3)新建控制单元

    @RequestMapping("/showForm")
    public String showForm(){
        return "/form.jsp";
    }

(4)新建页面:

        在上面使用taglib执行引入标签库

  <spring:message code="属性文件的key"></spring:message>根据语言环境负责加载属性文件中key的值。中文环境就加载suiyi_zh_CN.properties文件内容,英文环境就加载suiyi_en_US.properties文件内容

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <form action="" method="post">
        <spring:message code="bjsxt.username"></spring:message> <input type="text" name="username"/><br/>
        <spring:message code="bjsxt.password"></spring:message> <input type="text" name="password"/><br/>
        <input type="submit" value="<spring:message code="bjsxt.submit"></spring:message>"/>
    </form>
</body>
</html>

十七、SpringMVC常见组件

DispatcherServlet:前端控制器。Spring MVC的入口,也是整个流程控制中心。其他组件由DispatcherServlet统一调度,降低了组件和组件之间的耦合度。

MultipartResovler:多部分处理器。文件上传时需要使用。

LocaleResolver:解决客户端的区域和时区问题。

ThemeResolver:主题解析器。刻提供自定义布局。

HandlerMapping: 映射处理器。主要负责处理URL,并找到对应的HandlerMethod。简单说就是找@RequestMapping注解中映射路径是否有和URL匹配的。

HandlerAdapter:适配器。负责调用具体的HandlerMethod。

HandlerExceptionResovler:异常处理器。异常处理,根据不同异常返回视图。

RequestToViewNameTranslator:从请求中获取到视图名称。

ViewResovler:视图解析器,负责解析字符串视图名和物理视图文件的。

FlashMapManager:主要用于存储属性的,本质是一个Map。多用在重定向时。FlashMap在重定向之前存储,重定向之后删除。

ModelAndView:模型和视图。Spring MVC中提供的模型和视图接口。

HandlerInterceptor:拦截器。拦截控制器资源的。

十八、SpringMVC运行原理 

1.客户端向服务端发起请求,Spring MVC总体入口DispatcherServlet进行请求分发。

2.DispatcherServlet把URL交给映射处理器HandlerMapping进行解析URL。并寻找是否具有对应的控制单元。

3.如果存在控制单元,执行拦截器Interceptor的预处理方法preHandle进行处理。如果不存在对应控制单元,拦截器不执行。

4.如果拦截器允许放行,返回给Servlet分发器DispatcherServlet,调用后续组件。

5.Servlet分发器DispatcherServlet使用适配处理器HandlerAdapter,调用具体的控制单元方法。

6.执行单元方法HandlerMethod。

7.控制单元HandlerMethod执行完成后产生模型和视图组件ModelAndView。

8.返回模型和视图组件给Servlet分发器DispatcherServlet。如果有拦截器Interceptor,执行postHandle

9.Servlet分发器DispatcherServlet调用视图解析器ViewResolver解析视图

10.产生视图View对象,执行了视图对应的资源。

11.把结果返回给Servlet分发器DispatcherServlet。如果有拦截器Interceptor,执行拦截器的afterCompletion

12.Servlet分发器把最终视图执行结果响应给客户端浏览器。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值