JavaWeb——Servlet

1、 Servlet简介

Servlet(Server Applet)是 Java Servlet 的简称,称为小服务程序或服务连接器,是 Java 语言编写的服务器端程序,换句话说,Servlet 就是运行在服务器上的 Java 类。

处理请求和发送响应的过程是由一种叫做Servlet的程序来完成的,并且Servlet是为了解决实现动态页面而衍生的东西。

2、Servlet原理

在这里插入图片描述

  • 浏览器向服务器发送Get或Post请求
  • 服务器上的容器接收HTTP请求,从而产生两个对象:HttpServletRequest和HttpServletResponse
  • 加载servlet,实例化Servlet后,执行init()方法
  • 调用Servlet中的Service()方法
  • service()方法根据请求类型调用doGet()或者doPos()方法
  • doGet()或者doPost()处理后,由web容器调用返回给浏览器
  • 执行destroy(),销毁线程

我们在编写Servlet的时候,我们一般采用的是继承HttpServlet类的方式,Servlet有着这样的关系(HelloServlet为自己创建的类):
在这里插入图片描述

Servlet的mapping映射

这是注册的servlet:

<!-- 注册servlet-->
    <servlet>
        <!-- 类名 -->
        <servlet-name>hello</servlet-name>
        <!-- 所在的包 -->
        <servlet-class>com.sdablog.servlet.HelloServlet</servlet-class>
    </servlet>
  1. 一个Servlet可以指定一个映射路径
 <servlet-mapping>
    <servlet-name>Hello</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>

  1. 一个Servlet可以指定多个映射路径
 <servlet-mapping>
    <servlet-name>Hello</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>
 <servlet-mapping>
    <servlet-name>Hello</servlet-name>
    <url-pattern>/hello2</url-pattern>
  </servlet-mapping>
  1. 一个Servlet可以指定通用映射路径
 <servlet-mapping>
    <servlet-name>Hello</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

  1. 一个Servlet可以指定后缀或前缀映射路径
 <servlet-mapping>
    <servlet-name>Hello</servlet-name>
    <url-pattern>*.sd</url-pattern>
  </servlet-mapping>
  
 <servlet-mapping>
    <servlet-name>Hello</servlet-name>
    <url-pattern>/sd*</url-pattern>
  </servlet-mapping>
指定后缀时,前面不能有斜杠

优先级问题

如果指定了固定的路径映射,它的优先级会高于通配的路径映射。
严格来讲会根据"精确优先"进行匹配:

  • 路径模式包含的路径变量越少、通配符越少,SpringMVC认为它越精确。例如/hotels/{hotel}/*只包含一个路径变量和一个通配符,它会被认为比/hotels/{hotel}/**更精确,因为后者包含一个路径变量和两个通配符
  • 如果两个路径模式包含的路径变量和通配符数量相同,长度更长的路径模式会被认为更精确。例如/foo/bar*和foo/*两个路径模式都只包含一个通配符,但/foo/bar/*更长,因此它会被认为更精确
  • 当两个路径模式包含的路径变量和通配符总数相同时,通配符较少的路径模式会被认为更精确。例如/hotels/{hotel}和/hotels/*都只包含一个路径变量和通配符,但是前者只包含路径变量,没有包含通配符,因此他被认为更精确
    此外,还有以下的匹配规则:
    1. 路径模式几乎可以匹配任何请求地址,因此其它任何路径模式都比它更精确。例如.api/{a}/{b}/{c}虽然包含三个路径变量,但是依然比更精确。
      乎可以匹配任何请求地址,因此其它任何路径模式都比它更精确。例如.api/{a}/{b}/{c}虽然包含三个路径变量,但是依然比/**更精确。
    2. /public/几乎可以匹配/public/路径下的任何请求地址,因此任何不包含通配符的路径模式都比它更精确。例如/public/{a}/{b}/{c}虽然包含了三个路径变量,但它依然比前者更精确。

3、HelloServlet

Servlet接口在Sun公司有两个默认的实现类:HttpServlet、GenericServlet

项目工程需要引入的jar包:

<dependencies>
        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/jsp-api -->
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2.1-b03</version>
            <scope>provided</scope>
        </dependency>
</dependencies>
  1. 构建一个普通的Maven项目,删掉里面的src目录,以后学习的就在这项目里建立Moudel;这个空的工程就是Maven主工程。
    在这里插入图片描述

  2. pom.xml中parent标签的使用
    使用maven是为了更好的帮项目管理包依赖,maven的核心就是pom.xml。当我们需要引入一个jar包时,在pom文件中加上< dependency></ dependency>就可以从仓库中依赖到相应的jar包。但是子项目如果也需要这个jar包,如果又重新在子项目的pom文件增加,当jar版本发生改变,就需要修改父项目和子项目的jar包,这样就很麻烦,工作量也很大。此时就可以使用parent标签, 可以创建一个parent项目,打包类型为pom,parent项目中没有任何代码,只是管理多个项目之间公共的依赖。在子项目的pom文件定义< parent></ parent>,parent标签中写上parent项目的pom坐标就可以引用到common.jar了。

    父项目的java,子项目可以直接使用

父项目中会有

<groupId>com.sdablog</groupId>
<artifactId>javaweb-servlet</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
	<module>servlet-01</module>
</modules>

子项目中会有

  <parent>
    <groupId>com.sdablog</groupId>
    <artifactId>javaweb-servlet</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

标签中写上springmvc.jar的坐标,不需要写版本号,可以依赖到这个jar包了。这样springmvc.jar的版本发生变化时只需要修改parent中的版本就可以了。

  1. Maven环境优化

    1. 修改web.xml为最新
    2. 将maven的结构搭建完整
      在这里插入图片描述
  2. 编写一个Servlet程序

    1. 编写一个普通类
      在这里插入图片描述
    2. 实现Servlet接口,这里直接继承HttpServlet
public class HelloServlet extends HttpServlet {
    //由于get或post只是请求实现的不同方式,可以相互调用,业务逻辑都一样;
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();  //响应流
        writer.println("Hello,Servlet");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}
  1. 编写Servlet的映射
    为什么需要映射:我们写的是java程序,但是要通过浏览器访问,而浏览器需要连接Web服务器,所以我们需要在web服务器中注册我们写的Servlet,还需要给他一个浏览器访问的路径。

在web.xml文件中配置servlet映射标签

<web-app>
    <display-name>Archetype Created Web Application</display-name>
    <!-- 注册servlet-->
    <servlet>
     	<!-- 类名 -->
        <servlet-name>hello</servlet-name>
  		<!-- 所在的包 -->        
        <servlet-class>com.sdablog.HelloServlet.ServletHttp</servlet-class>
    </servlet>
    <!-- servlet的请求路径-->
    <servlet-mapping>
  		<!-- 访问的网址 -->
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>
  1. 配置Tomcat
    注意:配置项目发布的路径就可以
    在这里插入图片描述
  2. 启动测试

4、Servlet技术特点

Servlet技术带给程序员最大的优势是它可以处理客户端传来的HTTP请求,并返回一个响应

Servlet是一个Java类,Java语言能够实现的功能,Servlet基本上都可以实现(图形界面除外)。总的来说,Servlet技术具有以下特点

  1. 高效。在服务器上仅有一个Java虚拟机在运行,它的优势在于当多个来自客户端的请求进行访问时,Servlet为每个请求分配一个线程而不是进程。

  2. 方便。Servlet提供了大量的实用工具例程,例如处理很难完成的HTML表单数据、读取和设置HTTP头、处理Cookie和跟踪会话等。

  3. 跨平台。Servlet是用Java类编写的,它可以在不同的操作系统平台和不同的应用服务器平台下运行。

  4. 灵活性和可扩展性。采用Servlet开发的Web应用程序,由于Java类的继承性、构造函数等特点,使得其应用灵活,可随意扩展。

  5. 共享数据。Servlet之间通过共享数据可以很容易地实现数据库连接池。它能方便地实现管理用户请求,简化Session和获取前一页面信息的操作。而在CGI之间通信则很差。由于每个CGI程序的调用都开始一个新的进程,调用间通信通常要通过文件进行,因而相当缓慢。同一台服务器上的不同CGI程序之间的通信也相当麻烦。

  6. 安全。有些CGI版本有明显的安全弱点。即使是使用最新的标准和PERL等语言,系统也没有基本安全框架。而Java定义有完整的安全机制,包括SSL\CA认证、安全政策等规范。

5、ServletContext应用

当web应用启动的时候,自动创建ServletContext对象,它代表了当前的web应用。

由于一个web应用中的所有servlet共享同一个ServletContext对象,因此servlet对象之间可以通过servletontext对象来实现通讯。servletContext对象通常也被称为context域对象。

ServletContext对象相当于一个管理员,也可以说是一个“中介”,它可以保存一些东西,将各个servlet应用联系到一起。是一个公用的空间

servletcontext用法小结:
获取:this.getServletContext(建议用这个)、this.getServletConfig.getServletContext();
//添加属性
servletcontext.setAttribute(string,object);
//取出属性
servletcontext.getAttribute(“属性名”);
//删除
servletcontext.removeAttribute(“属性名”);

在这里插入图片描述

通过在ServletConfig中调用getServletContext方法,也可以获得ServletContext对象。
作用:

5.1、 共享数据

例:A这个Servlet中保存的数据,B这个Servlet中拿到;

存放的类:

public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        String username = "小明";
        context.setAttribute("username", username);  //将一个数据保存在username中,值为username

    }
}

读取的类:

public class GetServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        //获取一个名为username的值
        String username = (String) context.getAttribute("username");
        //设置头文件
        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf-8");
        
        resp.getWriter().print("名字=" + username);
    }
}

注:先存后取。

5.2、获取web应用的初始化参数

web.xml

 <!-- 配置该项目所有的servlet均可以使用的参数 ,写在servlet域外面-->
 <context-param>
 	<!-- 名称-->
    <param-name>sex</param-name>
	<!-- 内容-->
    <param-value>famale</param-value>
 </context-param>

在web应用中获取sex的内容:String sex=this.getServletContext().getInitParameter(“sex”);

5.3、请求转发

  1. 请求转发:你去一家餐厅吃饭,想吃什么菜点了服务员给你送来,你不用直接去端你的菜。
    在这里插入图片描述
    请求转发:
ServletContext context = this.getServletContext();
 // 转发的请求路径
RequestDispatcher requestDispatcher = context.getRequestDispatcher("/getc");
// 调用forward实现请求转发
requestDispatcher.forward(req, resp);

this.getServletContext().getRequestDispatcher("/url").forward(request, response);
request:请求对象
response:响应对象

5.4、读取资源文件

读取资源文件要根据资源文件所在的位置分为两种情况:

  1. 读取资源文件(读web目录下和WEB-INF目录下的文件)

无论在java目录下还是resources目录下创建的文件,编译后都会被打包在:classes路径下,这个路径俗称为classpath
在这里插入图片描述

读取资源文件:

InputStream stream = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");
        //创建属性对象
        Properties properties = new Properties();
        properties.load(stream);
        //读取属性
        String user = properties.getProperty("username");
        String pwd = properties.getProperty("password");
        resp.getWriter().print(user + ":" + pwd);

6、HttpServletResponse

web服务器接收到客服端的请求,针对这个请求,分别创建一个代表请求的HttpServletRequest对象

,代表响应的一个HttpServletReqsponse;

  • 如果要获取客户端请求过来的参数:找HttpServletRequest
  • 如果要给客户端响应一些信息:找HttpServletResponse

1. 简单分类

  1. 负责向浏览器发送数据的方法
public ServletOutputStream getOutputStream() throws IOException;
public PrintWriter getWriter() throws IOException;
  1. 负责向浏览器发送响应头的方法
//设置响应头编码
public void setCharacterEncoding(String charset);
//设置响应头的字符串长度
public void setContentLength(int len);
//设置长度
public void setContentLengthLong(long len);
//设置类型
public void setContentType(String type);

2. 常见应用

  1. 向浏览器输出消息
  2. 下载文件
    1. 要获取下载文件的路径
    2. 获取下载文件名
    3. 设置让浏览器能够支持下载我们需要的东西
    4. 获取下载文件的输入流
    5. 创建缓冲区
    6. 获取OutputStream对象
    7. 将FileOutputStream流写入buffer缓冲区
    8. 使用OutputStream将缓冲区中的数据输出到客户端
//    1. 要获取下载文件的路
        String realPath = "F:\\java\\Maven\\javaweb-servlet\\response\\src\\main\\resources\\img.png";
        System.out.println("下载文件的路径:"+realPath);
//    2. 获取下载文件名
        String fileName = realPath.substring(realPath.lastIndexOf("\\") + 1);
//    3. 设置让浏览器能够支持下载我们需要的东西
        resp.setHeader("Content-disposition", "attachment;filename=" + fileName);
//    4. 获取下载文件的输入流
        FileInputStream in = new FileInputStream(realPath);
        
//    5. 创建缓冲区
        int len = 0;
        byte[] buffer = new byte[1024];
        
//    6. 获取OutputStream对象
        ServletOutputStream out = resp.getOutputStream();

//    7. 将FileOutputStream流写入buffer缓冲区,使用OutputStream将缓冲区中的数据输出到客户端
        while ((len = in.read(buffer))>0){
            out.write(buffer,0,len);
        }
        in.close();
        out.close();

扩展知识:

  1. Java substring():截取父字符串的某一部分

public String substring(int beginIndex):从位置 beginIndex 向后获取内容
public String substring(int beginIndex, int endIndex) :从 beginIndex 到 endIndex 区间获取内容

参数

  • beginIndex – 起始索引(包括), 索引从 0 开始。

  • endIndex – 结束索引(不包括)。

public static void main(String[] args) {
        String str = "this is text";
        //从0到5的位置获取内容
        String sub = str.substring(0, 5);
        System.out.println(sub);    // is text
        
        //从位置4开始向后获取内容
        String str1 = str.substring(4);
        System.out.println(str1);   // is text
    }
  1. lastIndexOf 和 IndexOf 函数

    1.  lastIndexOf():在字符串中根据搜索条件来返回其在字符串中的位置,**空格也计数**,如果字符串中没有这样的字符,返回-1。
    

strObj.lastIndexOf(int ch) ,返回指定字符在此字符串中最后一次出现处的索引。
strObj.lastIndexOf(int ch , int fromIndex) ,返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向搜索。

参数:
strObj:必选项,String 对象或文字。
substring :必选项。要在 String 对象内查找的子字符串。
startindex :可选项。该整数值指出在 String 对象内进行查找的开始索引位置。假如省略,则查找从字符串的末尾开始。

String str = "01234567890123456789";

//查找字符串“01234567890123456789”中字符‘8’所在的位置(索引从0开始,从前往后搜索)
System.out.println(str.lastIndexOf('8'));   //18

//查找字符串“01234567890123456789”中字符‘8’所在的位置,从索引为9的位置,即“0123456789”,从后往前搜索。
System.out.println(str.lastIndexOf('8', 9)); //8
  2. IndexOf ():返回 String 对象内第一次出现子字符串的字符位置。这里和lastIndexOf 道理相反,就不写了。

3. 验证码功能

验证码怎么来的?

  1. 前端实现
  2. 后端实现,需要用到java的图片类,生成一个图片
public class ImageServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //如何让浏览器3秒刷新一次
        resp.setHeader("refresh", "3");

        //在内存中创建一个图片
        BufferedImage image = new BufferedImage(200, 180, BufferedImage.TYPE_INT_RGB);
        //得到图片
        Graphics2D g = (Graphics2D) image.getGraphics();
        //设置背景颜色
        g.setColor(Color.white);
        g.fillRect(0, 0, 200, 180);
        //给图片写数据
        g.setColor(Color.BLUE);
        g.setFont(new Font(null, Font.BOLD, 20));
        g.drawString(makeNum(), 0, 20);

        //告诉浏览器这个请求用图片的方式打开
        resp.setContentType("image/jpeg");
        //网站存在缓存,不让浏览器缓存
        resp.setDateHeader("expires", -1);
        resp.setHeader("Cache-Control", "no-cache");
        resp.setHeader("Pragma", "no-cache");

        //把图片写给浏览器
        ImageIO.write(image, "jpg", resp.getOutputStream());

    }

    //生成随机数
    private String makeNum() {
        Random random = new Random();
        String num = random.nextInt(9999999) + "";
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 7 - num.length(); i++) {
            sb.append("0");
        }
        num = sb.toString() + num;
        System.out.println(num);
        return num;
    }
}

4. 实现重定向

重定向:你去吃自助餐,问服务员匹萨在哪,服务员告诉你在哪,然后你去端。
在这里插入图片描述
一个web资源收到客户端请求后,他会通知客户端去访问另一个web资源,这个过程叫重定向。
常见场景:

  • 用户登录

public void sendRedirect(String location) throws IOException;

/*
解析:
	//响应头部设置一个Location属性,值就是重定向路径
	resp.setHeader("Location", "/re/img");
	//设置状态码
	resp.setStatus(302);
*/ 
//‘re’是当前项目路径
resp.sendRedirect("/re/img");  //重定向

面试题: 请你聊聊重定向和转发的区别?
相同点: 页面都会跳转
不同点:

  • 请求转发的时候,url不会产生变化;
  • 重定向的时候url会发生变化;

实践:
用重定向做一个用户登录逻辑。

前端登录页面:

<html>
<body>
<h2>Hello World!</h2>

<%--这里提交的路径,需要寻找到项目的路劲--%>
<%-- ${pageContext.request.contextPath} 代表当前的项目--%>
<form action="${pageContext.request.contextPath}/login" method="get">
    USER:<input type="text" name="username"> <br>
    Password:<input type="password" name="password"> <br>
    <input type="submit" value="login">
</form>

</body>
</html>

后端处理数据函数:

public class RequestTest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("进入这个请求");
        //处理请求,获取客户端请求的数据
        String username = req.getParameter("username");
        String password = req.getParameter("password");

        System.out.println(username + " : " + password);

        //重定向时候一定要注意路径问题,否则报404
        resp.sendRedirect("/re/success.jsp");
    }

这里补充一个基础的知识,以前没怎么注意。
java中不等于的逻辑运算,数字可用用 != 符号进行判断,但是要判断字符串的话,需要用equals()方法

public boolean equals(Object anObject)

  • anObject – 与字符串进行比较的对象
  • 如果给定对象与字符串相等,则返回 true;否则返回 false。

7、HttpServletRequest

HttpServletRequest代表客户端的请求,用户通过Http协议访问服务器,HTTP请求中的所有信息会被封装到HttpServletRequest,通过这个HttpServletRequest的方法,获得客户端的所有信息。

  • 获得客户机信息:
方法说明
getRequestURL()返回客户端发出请求时的完整URL。
getRequestURI()返回请求行中的参数部分。
getQueryString ()方法返回请求行中的参数部分(参数名+值)
getRemoteHost()返回发出请求的客户机的完整主机名。
getRemoteAddr()返回发出请求的客户机的IP地址。
getPathInfo()返回请求URL中的额外路径信息。额外路径信息是请求URL中的位于Servlet的路径之后和查询参数之前的内容,它以"/"开头。
getRemotePort()返回客户机所使用的网络端口号。
getLocalAddr()返回WEB服务器的IP地址。
getLocalName()返回WEB服务器的主机名。
  • 获得客户机请求头
方法说明
getHeader(string name)以 String 的形式返回指定请求头的值。如果该请求不包含指定名称的头,则此方法返回 null。如果有多个具有相同名称的头,则此方法返回请求中的第一个头。头名称是不区分大小写的。可以将此方法与任何请求头一起使用
getHeaders(String name)以 String 对象的 Enumeration 的形式返回指定请求头的所有值
getHeaderNames()返回此请求包含的所有头名称的枚举。如果该请求没有头,则此方法返回一个空枚举
  • 获得客户机请求参数
方法说明
getParameter(String name)根据name获取请求参数(常用)
getParameterValues(String name)根据name获取请求参数列表(常用)
getParameterMap()返回的是一个Map类型的值,该返回值记录着前端(如jsp页面)所提交请求中的请求参数和请求参数值的映射关系。(编写框架时常用)

7.1、 获取前端数据

通过getParameter和getParameterValues获取前端的数据,用一个登录demo例子,用请求转发的方式登录页面。

前端页面jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录</title>
</head>
<body>
<h1>登录</h1>
<form action="${pageContext.request.contextPath}/login" method="post">
    用户名:<input type="text" name="username"> <br>
    密码:<input type="password" name="password"> <br>
    爱好:
    <input type="checkbox" name="hobbys" value="打球"> 打球
    <input type="checkbox" name="hobbys" value="游泳"> 游泳
    <input type="checkbox" name="hobbys" value="玩游戏"> 玩游戏
    <input type="checkbox" name="hobbys" value="听音乐"> 听音乐
    <input type="checkbox" name="hobbys" value="打代码"> 打代码
    <input type="checkbox" name="hobbys" value="看电影"> 看电影
    <br>
    <input type="submit" value="登录">
</form>

</body>
</html>

获取前端数据:

public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        resp.setCharacterEncoding("utf-8");
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String[] hobbys = req.getParameterValues("hobbys");
        System.out.println(username+":"+password);
        System.out.println(Arrays.toString(hobbys));

        //通过请求转发,跳转到success页面
        //请求转发的路径 / 代表当前的web应用
        req.getRequestDispatcher("/success.jsp").forward(req,resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

注:这里有个坑,请求转发的符号“ / ”代表的是当前项目的路径,所以不用写当前项目的路径名,直接 / 就行。

小章总结:客户端将信息封装到request里,发送给服务器,让后服务器有各种方法函数将里面的信息取出。

8、总结

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值