Javaweb的总结(上)

最近开始学习了Javaweb,东西有点多,先来梳理一下相关的知识

  • 什么是Servlet?
  • Tomcat的运行过程
  • 什么是ServletConfig?
  • 什么是ServletContext?
  • Servlet的生命周期 
  • 注解的使用 
  • 模板方法的设计模式 
  • request 请求对象...... 

 什么是Servlet

 Servlet其实是一个接口,任何实现了Servlet接口的类,都被称为Servlet类

而所有的servlet类都由服务器调度,创建和销毁,无需我们程序员干预,要注意,我们自己定义的Servlet类,也就是我们自己new出来,而不是由服务器创建的servlet类,不受服务器管理

服务器有很多,我这里使用的是Tomcat服务器,我们可以把服务器理解为一个容器,它存储着很多应用,而应用是由Servlet类组合形成的

服务器和浏览器遵循着Http协议

Servlet类(实现类)和数据库遵循着JDBC协议

而服务器和Servlet类遵循着Servlet规范,也就是说,Servlet规范是服务器与实现类之间的协议

上图:

 我们只需要记住,实现了Servlet接口的类,被称为Servlet类

而实现了Servlet的类,就是项目的功能实现 

Tomcat的运行过程

我这里画了一个图,展示了浏览器,服务器,Servlet与数据库的实现过程

 

 首先用户在浏览器客户端输出网址,服务器也就是tomcat获取网址,通过tcp协议获取端口,项目名,和跳转连接,来访问对应的项目,比如我输入的是taobao,那么就会访问taobao的项目,如果输入的是xiaohongshu,那么就会访问xiaohongshu的项目,找到对应的项目后,会首先访问index.html(服务器的配置文件),当我们点击登录,访问login.html,实际的登录操作,是由登录这个Servlet类实现的,该类就需要拿到用户输入的数据,去数据库进行操作,最后将响应返回到浏览器客户端上。

什么是ServletConfig

在Tomcat中我们需要实现一个配置文件,web.xml文件,里面存放着许多配置信息

 用户输入链接是怎么执行相应的Servlet类呢,就是通过web.xml文件

用户输入....../dept/list该链接,就会获取其链接的名字,通过该名字去找其名字对应的class文件,通过反射执行Servlet类,所以,链接的命名和class文件的命名必须一样 

被servlet和servlet-mapping包裹的信息,注意是一对,被称为一个ServletConfig对象,一个Servlet类对应一个ServletConfig对象。

什么是ServletContext

一个Servlet对象对应一个ServletConfig。100个Servlet对象则对应100个ServletConfig对象。只要在同一个webapp当中,只要在同一个应用当中,所有的Servlet对象都是共享同一ServletContext对象

ServletContext被称为Servlet上下文对象

一个ServletContext对象通常对应的是一个web.xml文件

Servlet的生命周期 

实现Servlet接口有几个重要的方法:

Servlet对象像一个人的一生:

Servlet的无参数构造方法执行:标志着你出生了。

Servlet对象的init方法的执行:标志着你正在接受教育。

Servlet对象的service方法的执行:标志着你已经开始工作了,已经开始为人类提供服务了。

Servlet对象的destroy方法的执行:标志着临终。有什么遗言,抓紧的。要不然,来不及了。

Servlet类中方法的调用次数?

构造方法只执行一次。

init方法只执行一次。

service方法:用户发送一次请求则执行一次,发送N次请求则执行N次。

destroy方法只执行一次。

这个文章描述的很详细!

Servlet 的生命周期详解_servlet的生命周期-CSDN博客

例子

下面我写一个简单的例子,在页面进行增删改查的操作

 web.xml文件

<?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>list</servlet-name>
        <servlet-class>com.servlet.DepListServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>list</servlet-name>
        <url-pattern>/dept/list</url-pattern>
    </servlet-mapping>

</web-app>

这里我简单的配置页面信息,作为页面的展示页,为一个servlet起名为list,当用户访问/dept/list时候就会执行对应的servlet

DepListServlet类

package com.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import oa.tool.ConnectionGet;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class DepListServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //获取应用的根路径
        String contextPath = request.getContextPath();

        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.print("<!DOCTYPE html>");
        out.print("<html>");
        out.print("<head>");
        out.print("<meta charset='utf-8'>");
        out.print("<title>部门列表页面</title>");
        out.print("</head>");
        out.print("<body>");
        out.print("<script type='text/javascript'>");
        out.print("function del(dno){");
        out.print("if(window.confirm('删除将不可恢复')){");
        out.print("document.location.href = '" + contextPath +"/dept/delete?deptho=' + dno");
        out.print("}");
        out.print("}");
        out.print("</script>");
        out.print("<h1 align='center'>部门列表</h1>");
        out.print("<hr>");
        out.print("<table border='1px' align='center' width='50%'>");
        out.print("    <tr>");
        out.print("        <th>序号</th>");
        out.print("        <th>部门编号</th>");
        out.print("        <th>部门名称</th>");
        out.print("        <th>部门位置</th>");
        out.print("        <th>操作</th>");
        out.print("    </tr>");


        try {
            Connection connection = ConnectionGet.getConnection();
            String sql = "select * from dept;";
            PreparedStatement ps = connection.prepareStatement(sql);
            ResultSet resultSet = ps.executeQuery();
            int i = 0;
            while (resultSet.next()) {
                String deptno = resultSet.getString("deptno");
                String dname = resultSet.getString("dname");
                String loc = resultSet.getString("loc");
                out.print("    <tr>");
                out.print("        <td>" + (++i) + "</td>");
                out.print("        <td>" + deptno + "</td>");
                out.print("        <td>" + dname + "</td>");
                out.print("        <td>" + loc + "</td>");
                out.print("        <td>");
                out.print("            <a href='javascript:void(0)' onclick='del(" + deptno + ")'>删除</a>");
                out.print("            <a href='" + contextPath + "/dept/modify?deptno=" + deptno + "'>修改</a>");
                out.print("            <a href='" + contextPath + "/dept/details?deptno=" + deptno + "'>详情</a>");
                out.print("        </td>");
                out.print("    </tr>");
            }
            ConnectionGet.close(connection, ps, resultSet);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        out.print("</table>");
        out.print("<hr>");
        out.print("<a href='"+contextPath+"/add.html'>新增部门</a>");
        out.print("</body>");
        out.print("</html>");

    }
}

这是一个页面展示的servlet,首先规定了输出,转化为HTML语言,try块里才是这个servlet的主要逻辑,其实就是对JDBC的使用,连接数据库并输出到页面上。

删除,修改,详情也是同理,都是对JDBC的使用,我就不去展示了

我们下面还需要写,删除servlet,修改servlet,详情 servlet,新增servlet,我们的xml文件会很多很多,不能写一个servlet就要配置一个吧,完成后这个文件可能几m了,而且重复工作很费力,这时我们需要一个功能,注解式开发

注解的使用

注解的使用将不再在web.xml中配置servlet,我们通过注解的方式,来指定每个servlet对应的url,如下所示

package com.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import oa.tool.ConnectionGet;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

@WebServlet("/dept/list")
public class DepListServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //获取应用的根路径
        String contextPath = request.getContextPath();

        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.print("<!DOCTYPE html>");
        out.print("<html>");
        out.print("<head>");
        out.print("<meta charset='utf-8'>");
        out.print("<title>部门列表页面</title>");
        out.print("</head>");
        out.print("<body>");
        out.print("<script type='text/javascript'>");
        out.print("function del(dno){");
        out.print("if(window.confirm('删除将不可恢复')){");
        out.print("document.location.href = '" + contextPath +"/dept/delete?deptho=' + dno");
        out.print("}");
        out.print("}");
        out.print("</script>");
        out.print("<h1 align='center'>部门列表</h1>");
        out.print("<hr>");
        out.print("<table border='1px' align='center' width='50%'>");
        out.print("    <tr>");
        out.print("        <th>序号</th>");
        out.print("        <th>部门编号</th>");
        out.print("        <th>部门名称</th>");
        out.print("        <th>部门位置</th>");
        out.print("        <th>操作</th>");
        out.print("    </tr>");


        try {
            Connection connection = ConnectionGet.getConnection();
            String sql = "select * from dept;";
            PreparedStatement ps = connection.prepareStatement(sql);
            ResultSet resultSet = ps.executeQuery();
            int i = 0;
            while (resultSet.next()) {
                String deptno = resultSet.getString("deptno");
                String dname = resultSet.getString("dname");
                String loc = resultSet.getString("loc");
                out.print("    <tr>");
                out.print("        <td>" + (++i) + "</td>");
                out.print("        <td>" + deptno + "</td>");
                out.print("        <td>" + dname + "</td>");
                out.print("        <td>" + loc + "</td>");
                out.print("        <td>");
                out.print("            <a href='javascript:void(0)' onclick='del(" + deptno + ")'>删除</a>");
                out.print("            <a href='" + contextPath + "/dept/modify?deptno=" + deptno + "'>修改</a>");
                out.print("            <a href='" + contextPath + "/dept/details?deptno=" + deptno + "'>详情</a>");
                out.print("        </td>");
                out.print("    </tr>");
            }
            ConnectionGet.close(connection, ps, resultSet);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        out.print("</table>");
        out.print("<hr>");
        out.print("<a href='"+contextPath+"/add.html'>新增部门</a>");
        out.print("</body>");
        out.print("</html>");

    }
}

没有任何变化,只是删除了配置文件的配置信息,然后在类上添加@WebServlet("/dept/list"),也就是配置文件中url-pattern对应的路径。

模板设计模式

我们看到我们这个包下,CRUD的操作就需要这么多类,以后如果有需要是不是要好多好多类,出现类爆炸的问题,所以我们能不能将这些servlet归到一个类中,统一进行管理呢?

还记得我们之前写的学生管理系统的主页面吗,如果输入是1,就展示,如果输入的是2,就添加,一个数字对应一个方法,其实这就是模板设计模式,提取出主干,在主干上添加方法

 我们将对应的url统一添加到整体的servlet上,通过用户输入的地址,来找到输入地址的对应方法。

重定向与转发

在一个web应用中通过两种方式,可以完成资源的跳转:

第一种方式:转发
第二种方式:重定向

转发和重定向有什么区别?

代码上有什么区别?

转发:

// 获取请求转发器对象
RequestDispatcher dispatcher = request.getRequestDispatcher("/dept/list");
// 调用请求转发器对象的forward方法完成转发
dispatcher.forward(request, response);

// 合并一行代码
request.getRequestDispatcher("/dept/list").forward(request, response);
// 转发的时候是一次请求,不管你转发了多少次。都是一次请求。
// AServlet转发到BServlet,再转发到CServlet,再转发到DServlet,不管转发了多少次,都在同一个request当中。
// 这是因为调用forward方法的时候,会将当前的request和response对象传递给下一个Servlet。

重定向: 

// 浏览器发送请求,请求路径上是需要添加项目名的。
// 以下这一行代码会将请求路径“/oa/dept/list”发送给浏览器
// 浏览器会自发的向服务器发送一次全新的请求:/oa/dept/list
response.sendRedirect("/oa/dept/list");

转发(一次请求):
在浏览器地址栏上发送的请求是:http://localhost:8080/servlet/a ,最终请求结束之后,浏览器地址栏上的地址还是这个。没变。


重定向(两次请求):


在浏览器地址栏上发送的请求是:http://localhost:8080/servlet/a ,最终在浏览器地址栏上显示的地址是:http://localhost:8080/servlet/b

转发和重定向的本质区别?

转发:是由WEB服务器来控制的。A资源跳转到B资源,这个跳转动作是Tomcat服务器内部完成的。
重定向:是浏览器完成的。具体跳转到哪个资源,是浏览器说了算。

我们可以这样理解:

转发是在服务器内部做的事情,浏览器只关心最后响应的结果,也就是说用户访问a的servlet,a的servlet结束后会再去执行到b的servlet,最后响应的是b的servlet,但是浏览器显示的是a的servlet

而重定向就是访问两次不同的服务器,发送了两次请求,每次发送请求给指定服务器的时候都是使用的是不同的Request请求对象和Response响应对象,所以整个请求过程中,数据是不共享的

转发和重定向应该如何选择?什么时候使用转发,什么时候使用重定向?

如果在上一个Servlet当中向request域当中绑定了数据,希望从下一个Servlet当中把request域里面的数据取出来,使用转发机制。
剩下所有的请求均使用重定向。(重定向使用较多。)(因为还有Session)

域 

在详细说Cookie和Session之前,我们来说一下我们目前遇到的作用域

request(对应的类名:HttpServletRequest) 请求域(请求级别的)
session(对应的类名:HttpSession) 会话域(用户级别的)
application(对应的类名:ServletContext) 应用域(项目级别的,所有用户共享的。)


 这三个域对象的大小关系
request < session < application


他们三个域对象都有以下三个公共的方法:
setAttribute(向域当中绑定数据)
getAttribute(从域当中获取数据)
removeAttribute(删除域当中的数据)

Seesion

什么是session?
用户打开浏览器,进行一系列操作,然后最终将浏览器关闭,这个整个过程叫做:一次会话。会话在服务器端也有一个对应的java对象,这个java对象叫做:session。
什么是一次请求:用户在浏览器上点击了一下,然后到页面停下来,可以粗略认为是一次请求。请求对应的服务器端的java对象是:request。

session对象最主要的作用是:保存会话状态。(用户登录成功了,这是一种登录成功的状态,怎么把登录成功的状态一直保存下来呢?使用session对象可以保留会话状态。)

为什么需要session对象来保存会话状态呢?
因为HTTP协议是一种无状态协议。
什么是无状态:请求的时候,B和S是连接的,但是请求结束之后,连接就断了。为什么要这么做?HTTP协议为什么要设计成这样?因为这样的无状态协议,可以降低服务器的压力。请求的瞬间是连接的,请求结束之后,连接断开,这样服务器压力小。
只要B和S断开了,那么关闭浏览器这个动作,服务器知道吗?
不知道。服务器是不知道浏览器关闭的。

我们可以将session看成是一个浏览器对服务器访问,该浏览器对服务器的所有操作都称为一次会话(session)

session的实现原理:

当我们第一次访问服务器时,服务器会生成一个session对象,并给该session对象生成一个id,然后web服务器会将session的id发送给浏览器,浏览器将session的id保存在浏览器的缓存中。
session列表是一个Map,map的key是session-id,map的value是session对象。
用户第二次请求,自动将浏览器内存中的id发送给服务器,服务器根据id查找session对象。
关闭浏览器,内存消失,cookie消失,sessionid消失,会话等同于结束。

session的销毁:

浏览器关闭的时候,服务器是不知道的,服务器无法监session对象什么时候被销毁?测到浏览器关闭了,所以session的销毁要依靠session超时机制。但也有一种可能,系统提供了“安全退,用户可以点击这个按钮,这样服务器就知道你退出了,然后服务器会自动销毁session对象
第一种销毁:是超时销毁
第二种销毁:是手动销毁 

Cookie

session的实现原理中,每一个session对象都会关联一个sessionid,例如:
JSESSIONID=41C481F0224664BDB28E95081D23D5B8
以上的这个键值对数据其实就是cookie对象。
对于session关联的cookie来说,这个cookie是被保存在浏览器的“运行内存”当中。
只要浏览器不关闭,用户再次发送请求的时候,会自动将运行内存中的cookie发送给服务器。


例如,这个Cookie: JSESSIONID=41C481F0224664BDB28E95081D23D5B8就会再次发送给服务器。
服务器就是根据41C481F0224664BDB28E95081D23D5B8这个值来找到对应的session对象的。

cookie怎么生成?cookie保存在什么地方?cookie有啥用?浏览器什么时候会发送cookie,发送哪些cookie给服务器?

cookie最终是保存在浏览器客户端上的。

可以保存在运行内存中。(浏览器只要关闭cookie就消失了。)
也可以保存在硬盘文件中。(永久保存。)

cookie有啥用呢?

cookie和session机制其实都是为了保存会话的状态。
cookie是将会话的状态保存在浏览器客户端上。(cookie数据存储在浏览器客户端上的。)
session是将会话的状态保存在服务器端上。(session对象是存储在服务器上。)

举个例子,我们经常会遇到保存密码免登录,我们想一想这个过程是怎么实现的

第一次,用户输入密码和账户,那么服务端就会获取到该用户的信息,通过对数据库的连接查询,账户和密码是否正确,如果正确,则证明登录成功,也就这么这次会话将会开启,服务端会返回一个sessionID也就是Cookie给浏览器,还需要再进行判断,用户是不是勾选了免登录,如果勾选了,我们需要将密码和账户封装成Cookie返回给浏览器。

第二次,用户再次访问服务端,会携带Cookie,这时候我们需要进行一次判断,判断Cookie中有没有封装的Cookie密码和账户两个信息,如果有,则取出这两个信息,再次进行数据库查询判断,如果没有,则要重新登录。

思路:

1.判断浏览器是否携带了密码和账户的Cookie信息

如果没有,则进行登录操作 

如果有,则判断Cookie的账户密码信息是否正确,正确则登录到服务端,如果失败则重新登录

2.登录成功后返回一个session对象,保持通信,并判断是否用户是否勾选了免登录

如果有,则给浏览器返回封装的Cookie密码和账户

如果没有,则不会返回

代码如下

package com.movie.web.servlet;

import com.movie.bean.User;
import com.movie.utiles.DBUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.*;

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

@WebServlet("/welcome")
public class WelcomeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取浏览器的cookie
        //cookie可能有也可能没有,如果有,则其长度大于0
        Cookie[] cookies = request.getCookies();
        //用户账号
        String username = null;
        //用户密码
        String password = null;
        if (cookies != null){
            for (Cookie cookie : cookies){
                //获取cookie的名字
                String name = cookie.getName();
                if ("username".equals(name)){
                    //找到用户账号对应的数据
                    username = cookie.getValue();
                }else if ("password".equals(name)){
                    //找到用户密码对应的数据
                    password = cookie.getValue();
                }
            }
        }
        //判断username和password是否正确
        if (username != null && password != null){
            //验证cookie对应的账号密码是否正确
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            //标记
            boolean success = false;
            try {
                conn = DBUtil.getConnection();
                String sql = "select c_name,c_password from c_user where c_name = ? and c_password = ?;";
                ps = conn.prepareStatement(sql);
                ps.setString(1,username);
                ps.setString(2,password);
                rs = ps.executeQuery();
                if (rs.next()){
                    success = true;
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }finally {
                DBUtil.close(conn,ps,rs);
            }
            if (success){
                //应该成功了就要获取session,session会超时销毁
                HttpSession session = request.getSession();
                //封装用户
                User user = new User(username,password);
                //将登录成功的用户放入session域
                session.setAttribute("user",user);
                //跳转主页面
                response.sendRedirect(request.getContextPath()+"/record/list");
            }else {
                //跳转到登录页
                response.sendRedirect(request.getContextPath()+"/index.jsp");
            }
        }else {
            //证明cookie为空,跳转到登录页
            response.sendRedirect(request.getContextPath()+"/index.jsp");
        }
    }
}
    private void doLogin(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //标记是否登录成功
        boolean success = false;
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String d = request.getParameter("d");

        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        String sql;
        String jump;
        if ("1".equals(d)){
            sql = "select u_name,u_password from t_user where u_name = ? and u_password = ?";
            jump = "movie";
        }else {
            sql = "select c_name,c_password from c_user where c_name = ? and c_password = ?";
            jump = "record";
        }
        try {
            conn = DBUtil.getConnection();
            ps = conn.prepareStatement(sql);
            ps.setString(1,username);
            ps.setString(2,password);
            rs = ps.executeQuery();
            if (rs.next()){
                //这时证明登录成功
                success = true;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DBUtil.close(conn,ps,rs);
        }
        if (success){
            //因为登录成功,所以要给此用户凭证证明已经登录
            //所以必须获取到session,否则会被拦截
            //且此seesion表示当前用户已成功登录
            HttpSession session = request.getSession();
            //将此用户存入session域中,前端显示
            User user = new User(username,password);
            session.setAttribute("user",user);

            //登录成功,并且用户选择了“十天内免登录”
            String f = request.getParameter("f");
            if ("1".equals(f)){
                //创建Cookie对象存储登录名与密码
                Cookie cookie1 = new Cookie("username",username);
                Cookie cookie2 = new Cookie("password",password);
                //设置Cookie的有效时长
                cookie1.setMaxAge(60 * 60 * 24 * 10);
                cookie2.setMaxAge(60 * 60 * 24 * 10);
                //设置Cookie的路径(只要访问该项目。浏览器就要携带这两个cookie)
                cookie1.setPath(request.getContextPath());
                cookie2.setPath(request.getContextPath());
                //响应cookie给浏览器
                response.addCookie(cookie1);
                response.addCookie(cookie2);
            }
            //无论选择不选择免登录,最后都需要跳转到主页面
            response.sendRedirect(request.getContextPath()+"/"+jump+"/list");
        }else {
            //失败,跳转到失败页面
            response.sendRedirect(request.getContextPath()+"/error.jsp");
        }
    }

语法什么的我就不去详细说啦,希望大家能理解我的思路,首先我先获取浏览器请求所携带的所有Cookie,如果有账户和密码的内容,进行验证并跳转,如果没有,则跳转到登录界面。

登录界面对应的是登录servlet,首先先判断登录的是管理员还是普通用户,根据不同的权限去查询对应的表,然后判断是否登录成功,如果登录成功就要返回一个session,并判断是否选择了免登录,如果选择了则返回给浏览器一个Cookie对象,用于下次访问的判断,登录失败则跳转到失败页面,重新登录。

结尾

还有很多内容,我就不详细去说了,后面再说一下监听器与过滤器

这次web的学习我搭建的项目还是JavaSE总结的电影管理系统,对上一份代码进行了拓展和web的实现

谢谢大家的观看!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值