学习Servlet

 



Servlet:Server let

Servlet是用Java技术来实现CGI功能的编程.

Servlet介于浏览器(或其他HTTP客户端)与服务器之间,起到桥梁的作用.

Servlet的具体作用为:

(1) 读取客户端发送的数据

(2) 获取客户请求中所包含的信息.

(3) 产生响应结果,并将结果包含到一个文件中.HTML文件中.

(4) 设置HTTP响应参数.如告诉浏览器文件类型为HTML.

(5) 将文件返回给客户端.

(6) Servlet应用还可以于应用程序服务,或置入到邮件,FTP服务器程序中.

 

Servlet具有很多优点:

1.高效率

2.应用方便

3.功能强大

4.便携性好

5.安全

6.成本低

 

 

 

容器

Servlet容器

Servlet容器也被称为Servlet引擎.

Servlet容器是一个编译好的可执行程序,Web服务器与Servlet间的媒介.它负责将请求翻译成Servlet能够理解的形式.同时传给Servlet一个对象使之可以送回响应.容器也负责管理Servlet的生命周期.

 

Web服务器

Web服务器是能够处理Http请求的服务器,它可以提供静态页面,图像有的Web服务器也支持动态页面的生成,支持JSP,Servlet,具有JVM.

 

应用服务器

应用服务器可以处理基于多种协议的请求,包括Http协议应用服务器可以支持ServletJSP,以及所有其他J2EE服务,包括JNDI,EJBs,JMS.

 

 

web应用程序

Servlet,JSP及其支持文件都作为Web应用程序的组成部分进行部署.通常Web应用程序部署在Tomcatwebapps子目录中.一般Web应用程序的目录结构包括根目录和几个子目录.

子目录:

WEB-INF  该目录包含Web应用程序的部署描述文件.

WEB-INF/classes  该目录包括Web应用层序中所使的Servlet和实用工具类文件.如果类文件是包的组成部分,则包的目录结构从该目录开始.

WEB-INF/lib 该目录包含Java存档文件(JAR文件).JAR中是Servlet和使用工具类文件.

配置上下文目录之后,还必须配置Web应用程序.这种配置在web.xml文件中完成.可以在web.xml文件中制定各种配置参数:

调节Servlet的名称,Servlet的描述,Servlet的完全的类名和Servlet容器调用Servlet的路径.

修改web.xml的部署描述符后,需要重新启动Tomcat.否则,Tomcat将不能识别新的Web应用程序.

可以采用上述方法在webapps目录中创建子目录.

也可以把上述子目录捆绑到一个类型的WAR的存档文件中.WAR文件是以.war为扩展名的JAR文件.可以使用jar命令来创建它.与很多小文件相比,WAR采用单个大文件利于在服务器之间的转移.服务器开始运行时,会将放置在webapps目录中的WAR文件的内容解压缩到合适的webapps的子目录结构中.

 

 

Servlet API

javax.servlet包中

所有的Servlet必须实现javax.servlet.Servlet接口.

大部分Servlet通过继承javax.servlet.GenericServletjavax.servlet.Http.HttpServlet两个类中的一个来实现javax.servlet.Servlet接口.

Http.HttpServlet类继承于GenericServlet.

一般情况下,和协议无关的Servlet继承GenericServlet.

HTTP协议的Servlet继承Http.HttpServlet.

 

Servlet的基本结构

HttpServlet类是抽象类.它根据浏览器的请求(GET方法,POST方法等),复写doGet或者doPost方法

 

doGet,doPost方法相当于html页面的事件响应方法.

 

客户的每个请求都会引发新的线程.多个并发请求一般会导致多个线程同时调用service方法.通过单线程模式也可以规定任何时间仅允许一个线程运行.

 

 

 

Servlet的生命周期

对于每个线程的Servlet的生命周期

(1) 加载和实例化:

Servlet容器加载和实例化Servlet.

Servlet容器启动时或当Servlet容器检测到需要该Servlet的第一个请求时,创建Servlet实例.

容器是通过Java反射来创建Servlet实例的.所有编写Servlet,不该提供带参数的构造方法,

 

(2)  初始化:

Servlet实例化后容器调用init方法初始化.

初始化期间,Servlet实例使用容器准备的ServletConfig对象.使用ServletConfig可以从配置文件中获取基本初始化参数.从而完成客户请求前的前提工作, .如建立数据库连接等.

如发送错误,Servlet实例抛出ServletException异常或者UnavailableException异常通知容器.

ServletException用于指明一般初始化异常.例如,没有找到初始化参数.

UnavailableException用于通知容器该Servlet不可用.例如数据库服务器没有启动,数据库连接无法建立.

init方法在Servlet生命周期中只被调用一次.只有服务器通过destroy方法销毁Servlet,init方法才能被再次调用.

注意:若本类继承了父Servlet,init(ServletConfig)内需要先调用super.init(ServletConfig)程序.

服务器构造Servlet实例之后,即立刻调用init(ServletConfig)方法,执行Servlet初始化工作. ServletConfig对象包含Servlet初始化所需要的参数.

 

(3) 请求处理

客户端发送请求,服务器创建HttpServletRequest对象(包含请求信息)HttpServletResponse对象(包含响应信息).

然后,把两个对象作为参数调用Servletservice().service()方法根据收到HTTP请求的类型,调用doGet,doPost或者其他方法.

调用结束后,请求即结束,等待新的请求.若有新的请求,服务器重新创建HttpServletRequest, HttpServletResponse,然后再调用service().

service方法执行期间,如果发送错误,Servlet实例可以抛出ServletException异常或UnavailableException异常.

UnavailableException用于通知容器Servlet实例永久不可用,从而Servlet容器将调用destroy方法,释放该实例.此后对该实例的任何请求都将收到容器发送的HTTP404(请求的资源不可用).UnavailableException指定该实例暂时不可用,则在暂时不可以的时间段内对该实例的任何请求都将收到容器发送的HTTP503(服务器暂忙,不能处理请求).

 

(4) 服务终停

在服务器关闭或者不在需要Servlet,容器就会调用Servletdestroy()方法,以便释放该实例所使用的资源.例如停止后台运行的线程.Cookie列表和点击数写入磁盘,以及执行其他清理活动.

 

说明:

并不是为每个请求创建一个Servlet对象,而是根据请求创建不同的HttpServletRequest, HttpServletResponse,然后调用service().所有的请求共享相同的Servlet.

 

 

字符编码

对将要读取的request.进行字符编码.注意,务必在读取数据之前编码.

request.setCharacterEncoding("utf-8");

 

对要发送的response进行字符编码

response.setCharacterEncoding("utf-8");

 

 

与客户端交互

提取Servlet信息

在许多初始化过程中需要使用Servlet参数,这些参数在web.xml.

 

GenericServlet

通过String getInitParameter(String name)可以得到指定参数名的Servlet初始化参数数值.

若存在参数,则返回String.若不存在返回null.

 

通过String getInitParameterNames()可以得到web.xml中的所有的Servlet初始化参数.

方法返回Enumeration类实例.若不存在参数,返回null.

 

public ServletContext getServletContext()

返回当前servlet中的SercletContext对象的引用.

 

ServletConfig对象

ServletConfig.getServletContext().

可以从servletServletConfig 对象中得到上下文环境.

 

 

提取服务器信息

Servlet可以得到许多服务器信息.例主机名,监听端口,服务器平台等.同时可以将这些信息发送到客户端.

getServerName()方法返回服务器名称.

getServerPort()方法返回服务器端口.

getServerInfo ()方法返回服务程序的信息信息之间用”/”分开.

getAttribute()方法返回服务程序的属性.

 

 

提取客户端信息

服务器可以从request得到许多客户端信息.

String getRemoteAddr()方法,可以获得客户端IP地址.

String getRemoteHost()方法,可以获得客户端机器名称.

InetAddress.getByName()可以将IP或机器名转换为一个java.net.InetAddress对象.

更多的是要得知客户的请求.

 

表单提交

对客户请求的处理体现在对表单数据的处理上.请求的表单数据有 ”/对组成.格式为name/value,没对用&分开当提交某一个网页的时候常常在浏览器地址栏中看到一定格式的数据.

格式:parma1=value1& parma2=value2& parma3=value3……

ServletRequest对象的方法

Enumeration getParameterNames()

得到当前请求中所有参数的完整列表.

public String getParameter(String name);

返回参数name对应的参数值.String返回.

public String[] getParameterValues(String name);

若有多个相同的请求参数名,则把所有的值以String[]数组返回.若参数不存在,则返回null.

Servlet还有很多方法获取客户端的其他信息.

 

 

 

发送HTML信息

Servlet处理所获信息并将其响应发送给客户端.

Web服务器响应有一个状态行,一些响应报头,一个空行,响应文档构成.

 

一般响应如下:

HTTP/1.1 200 OK                         //状态行

Content-Type:text/html           //响应报头1

Header2:…                                   //响应报头2

…                                                     //….

HeaderN:…                                 

                                                        //一个空行

<!DOCTYPE HTML ….>      //响应文档

<html>

….

</html>

报头Content-Type:text/html 指定后面文档MIME类型,后面的其他报头可选.

 

 

所以在Servlet输出页面之前

 

 

 

客户端跟踪

HTTP是无状态协议,服务端不能自动维护客户连接的上下文信息.为了可以跟踪客户端状态.可以分别使用CookieSession.

使用Cookie

CookieWeb服务器保存在用户硬盘上的一段文本,Web服务器将它发送到浏览器.之后,当再次访问同一网络时,浏览器将它原封不动地返回.其中的信息片断以/对的形式存储.使用这种方法,网站可以维护客户的连接.

Cookie可以对客户短期跟踪;记录客户用户名和密码等.

1. 创建Cookie对象

Cookie的构造方法有两个参数,Cookie名称和Cookie.

Cookie login=new Cookie(“CookieName”,”John”);

 

2. 设置Cookie

:设置Cookie注释

login.setComment(“Cookie Coment”);

 

创建Cookie并将它发送到浏览器后,默认情况下,它是会话级的Cookie,仅仅存储在浏览器的内存中,用户退出浏览器后,Cookie将被删除.如果希望Cookie存储在磁盘上,则需要设定MaxAge.

:Cookie的生命设置为一天.

login.setMaxAge(60*60*24);    //时间以秒为单位

 

 

3. 发送Cookie到客户端

刚创建的Cookie存在于服务器内存中,必须将它发送到客户端,Cookie才能发挥作用.

发送Cookie,需要使用HttpServletResponseaddCookie方法,Cookie插入到一个HTTP响应报头.

发送的语句如下:

public void doGet(HttpServletRequest req,HttpServletResponse res){

Cookie login=new Cookie(“CookieName”,”John”);

login.setComent(“Cookie Coment”);

res.addCookie(login);

}

 

 

 

4. 从客户端读取Cookie

首先调用request.getCookies()得到一个Cookie对象数组;

然后调用每个CookiegetName()方法.调用getValue()方法得到相关Cookie的值.

 

 

使用Session

Session指的是在一段时间内,单个客户与Web服务器的一连串交互过程.

 

当客户第一次打开浏览器访问Servlet,容器中会生成一个新的HttpSession类对象.

HttpServletRequest对象的getSession()方法可得到该Session对象.得到Session对象后,就可以利用它没置会话参数.

例如:计算客户在整个会话过程中访问一个Servlet页面的次数.

得到的Session对象可以在以后的上下文无关的页面访问.

当第一次访问该Servletsession.isNew()返回true.以后均返回都是false.同时页面访同次数的值随着每次访间刷新网页而增1.

session的持续时间还与服务器有关。即便浏览器访问了其他网页.如再次调用这个session,仍然是同一个session.

 

:Session应用举例

用户登录购物网站后,用户浏览并选购多个商品,期间与系统交互并连续访问多个不同的网页.直到最后确认购买并支付货款.整个过程就是一个Session,期间用户的状态需要跟踪并保持.

 

Session存与服务器中,不在网络上传送.它的好处是可以用来记录客户的私有信息.并且在时间范围内不会消失.

Session的使用可以分为3个步骤.

1. 获得一个Session

调用HttpServletRequestgetSession()方法获得一个Session.

HttpSession session=request. getSession();

为了得到正确的会话,必须在发生任何文档到客户程序之前获得一个Session.

public void doGet(HttpServletRequest req,HttpServletResponse res){

    HttpSession session=request. getSession();

//….

PrintWrite out=response.getWrite();

}

 

 

2. 存储或读取存储

Session存与服务器中,不在网络上传送.Servlet容器会为HttpSession分配一个唯一标示符,称为Session ID.

Servlet容器将Session ID作为Cookie保存在客户的浏览器中.每次客户发生HTTP请求时,Servlet容器从HttpRequest对象中读取Session ID,然后根据Session ID在服务器端找到相应的HttpSession对象.从而获得客户的状态信息.

这样的Cookie叫做Session Cookie,存储于浏览器内存中的,并不是写到硬盘上的.Session Cookie针对某一次会话而言,会话结束Session Cookie也就随着消失了.

 

如果浏览器不支持Cookie,或者浏览器不接受Cookie,可以通过URL重写来实现会话管理.

实质上URL重写是通过向URL连接添加参数,并把Session ID作为值包含在连接中.

操作URL的方法.

response.encodeURL()

response.encodeRedirectURL()

 

Session对象拥有内建的数据结构,期中可以存储任意数量的name/value.期中name是字符串,valueJava语言的object对象.

session.getAttribute(“name”)  可以查找以前存储的值;若不存在则返回null.

session.setAttribute(“name”)  可以向session中设置相关信息.

session.removeAttribute(“name”)  移除相关值.

 

 

:

public class SessionServlet extends HttpServlet{

public void doGet(HttpServletRequest req,HttpServletResponse res){

    HttpSession session=request.getSession();

//….

SomeClass scl=(SomeClass)session.getAttribute(“name1”);

if(scl!=null){

scl=new SomeClass();

session.putAttribute(“name1”,cart);

}

//….

}

}

 

3. 销毁Session

Session可以手动删除,也可以自动销毁.

在一段时间(由服务器设定)内没有request的情况下,Web服务器会自动销毁Session.通过调研Sessioninvalidate()方法可以手动销毁Session.

 

 

 

Session的持久化

因某种需要服务器重启.为了维持重启后Session依旧存在通过对象序列化技术实现Session持久化.

在需要的时候(如关闭服务器)将内存中的session保持到持久化设备中.

当需要重新加载Session对象时,通过反序列化技术在内存中重新构造session对象.

即要求HttpSession类要实现java.io.Serializable接口,同时Seesion中保存的对象的类也要实现Serializable接口.

 

 

协作与通信

Servlet协作通讯第一步是获得分发器(dispatcher).

获得dispatcher:

RequestDispatcher dispatcher=getServletContext().getRequestDispatcher(“/SomePath/SomeString”);

通过设置字符串参数,可以获得Servlet,HTML,JSP等资源.

 

RequestDispatcher是一个接口.它有两个方法:

forword方法

void forward(ServletRequest request,ServletResponse response) throws ServletException,IOException

将本Servletrequest请求传递给相应资源响应.

利于该方法可以实现Servlet先预处理request,然后将处理后的request传递给另一个资源(servlet, JSP file, or HTML file)处理.

 

include方法

引入其他资源来响应客户端的请求.

 

 

Servlet间的信息共享

可以利用共享类对象实现信息共享,实现信息共享.

简单的方法是利用JavaProperties列表,实现信息共享.

Properties列表包含系统的很多信息,也可以包含与应用程序相关的很多属性.

添加属性到列表:

System.getProperties().put(“key”,”value”);

得到属性值

String value=System.getProperty(“key”);

删除列表的某个属性

System.getProperties().remove (“key”);

 

 

制作Servlet的完整过程

(1)编写Servlet

引入包:

import java.servlet.*;

import javax.servlet.http.*;

import java.io.*;    //通常情况下需要该包.

 

实现Servlet接口或继承HttpServlet

Servlet主要用于HTTP协议时,可以继承HttpServlet.

 

编写成员方法

Servlet通常无需成员变量,按实际需要,实现init(),doGet(),doPost(),destory()等方法.

 

(2)编译Servlet

把写好的Servlet编译成class文件.

注意java.servlet.* javax.servlet.http.*不是javaSE中的包.

 

(3)配置Servlet

配置web.xml.

Servlet的配置包括两部分,Servlet的声明与Servlet的访问方式.

首先是配置Servlet的声明,使用<servlet>

<servlet>

  <servlet-name>ServletNAME</servlet-name>

  <servlet-class>ServletClass</servlet-class>

</servlet>

 

<servlet-name>用于声明Servlet的名字

<servlet-class>用于声明Servlet所对应的类名..class文件的名称.如果class文件在包中,则需要把包写上,my.servlet. ServletClass

 

然后是配置Servlet的访问方式.

<servlet-mapping>

  <servlet-name> ServletNAME </servlet-name>

  <url-pattern>/hello</url-pattern>

</servlet-mapping>

<servlet-name>表示Servlet的名字,要与对应在Servlet声明的<servlet-name>一致.

<url-pattern>表示访问时候的格式,例如要访问这个Servlet需要使用hello作为名字访问. <servlet-name>,<url-pattern>可以不同.服务器接受到请求后根据url-pattern查找相应的Servlet.

 

(4)部署

即把相应的Servlet.class文件,web.xml以规定的文件格式放置到web容器的应用程序目录下(tomcat,文件放在webapps.)

 

 

(5)调用Servlet

通常Servlet可以使用以下方法调用。

通过URL调用Seavlet

客户(浏览器)使用以下格式的URL调用:

http: //Serverlocal:80/Servlet/ <servletURL>

Serverlocal表示主机名,IP.

:80 端口为80.端口号可以忽略.Http请求默认为80端口.

Servlet表示工程名称

<servletURL>表即相应的url-pattern.

<servletURL>的基本格式:ServletName? para1=value1& para2=value2....

其中,ServletName是相应的url-pattern.

若需要传递参数,则可以在后面跟上参数说明.

“?”后面跟是一串参数,para 1是第一个参数名,value1是它的值.&表示并列.para2是第二个参数名,value2是它的值,依此类推。

这种URL将以HttpGet方法提交给Servlet.

 

通过表单提交的形式访问

Html中当onsubmit事件激发,form中的内容被提交给服务器.

<form action=" " method=" " οnsubmit=" ">

action: 表示Servlet路径

method: 表示Http方法,postget.若没有method属性,则默认以get方法提交.

 

 

 

Servlet3个名字

servlet的文件路径名.servlet .class 文件的具体路径.服务器上的位置则决定了完整的路径名.

servlet的部署名称.

servlet的公共URL.

 

HttpServletResponse接口的两个方法

sendRedirect()方法

调用sendRedirect方法,会在响应中设置Location响应报头.浏览器的地址栏会发生改变.

 

forward()方法

浏览器不知道为其服务的Servlet已经换成了另一个Servlet.浏览器地址栏的URL不会改变.

 

 

 

 

Servlet的异常处理机制

Servlet,有两种服务器端的异常处理机制:声明式异常处理和程序式异常处理.

声明式异常处理

 

程序式异常处理

 

 

 

 

线程安全的Servlet

.多线程的Servlet模型

Servlet规范定义,在默认情况下(Servlet不是在分布式的环境中部署),Servlet容器对声明的每一个Servlet,只创建一个实例.

若存在多个客户请求这个Servlet.Servlet容器使用多线程技术处理多个请求.servlet容器维护了一个线程池来服务请求.

线程池实际上是等待执行代码的一组线程,这些线程叫做工作者线程.Servlet容器使用一个调度者线程来管理工作者线程.

当容器接受到一个访问Servlet的请求,调度者线程从线程池中选取一个工作者线程,将请求传递给该线程,然后这个线程执行Servletservice()方法.

当这个线程正在执行时,容器受到了另一个请求,调用者线程将从池中选取另一个线程来服务新的请求.注意,Servlet容器并不关心这第二个请求是访问同一个Servlet还是另一个Servlet.因此,如果容器同时接收到访问同一个Servlet的多个请求,则这个Servletservice()方法将在多个线程中并发地执行.

Servlet容器默认采用单例多线程方式,最大限度地减少了产生servlet实例的开销,显著提升了对请求的响应时间.对于Tomcat,可以在servlet.xml文件中通过<Connection>元素设置线程池中线程的数目.

 

SingleThreadModel接口

javax.servlet.SingleThreadModel接口没有任何方法,它是一个标识接口.如果servlet实现了该接口,servlet容器保证在每一时刻都只有一个小线程servlet实例的service()方法中运行.

该接口不能解决所有线程安全问题且容易让人忽视线程安全.该接口已经于servlet2.4规范中废除.

 

 

关于变量

    局部变量在栈中分配,每个线程都有自己的栈所以局部变量总是线程安全的.

    实例变量是在堆中分配的.被该实例的所有线程共享实例变量不是线程安全的.

    类变量不被实例化即可直接使用.所以类变量不是线程安全的.

   

关于属性

    请求对象的属性访问是线程安全的.Session和上下文对象的属性访问不是线程安全的.

 

JSP系统提供的8对象

application不是线程安全的.其他均为线程安全.

 

 

 

建议:

尽可能在Servlet中只使用本地变量

应该只适用只读的实例变量和本地变量

不要在Servlet中创建自己的线程

修改共享对象时,一定要使用同步,尽可能缩小同步代码的范围,不要直接在Service()方法或doXxx()方法上进行同步,以免影响性能.

如果在多个不同的servlet,要对外部对象(如文件)进行修改操作,一定要加锁,做到互斥的访问.

 

:

1. 变量的线程安全:这里的变量指字段和共享数据(如表单参数值)

a,将 参数变量 本地化。多线程并不共享局部变量.所以我们要尽可能的在servlet中使用局部变量。

例如:String user = “”;

user = request.getParameter(“user”);

b,使用同步块Synchronized,防止可能异步调用的代码块。这意味着线程需要排队处理。

在使用同板块的时候要尽可能的缩小同步代码的范围,不要直接在sevice方法和响应方法上使用同步,这样会严重影响性能。

2. 属性的线程安全:ServletContextHttpSessionServletRequest对象中属性

ServletContext:(线程是不安全的)

ServletContext是可以多线程同时读/写属性的,线程是不安全的。要对属性的读写进行同步处理或者进行深度Clone()

所以在Servlet上下文中尽可能少量保存会被修改(写)的数据,可以采取其他方式在多个Servlet中共享,比方我们可以使用单例模式来处理共享数据。

HttpSession:(线程是不安全的)

HttpSession对象在用户会话期间存在,只能在处理属于同一个Session的请求的线程中被访问,因此Session对象的属性访问理论上是线程安全的。

当用户打开多个同属于一个进程的浏览器窗口,在这些窗口的访问属于同一个Session,会出现多次请求,需要多个工作线程来处理请求,可能造成同时多线程读写属性。

这时我们需要对属性的读写进行同步处理:使用同步块Synchronized和使用读/写器来解决。

ServletRequest:(线程是安全的)

对于每一个请求,由一个工作线程来执行,都会创建有一个新的ServletRequest对象,所以ServletRequest对象只能在一个线程中被访问。ServletRequest是线程安全的。

注意:ServletRequest对象在service方法的范围内是有效的,不要试图在service方法结束后仍然保存请求对象的引用。

3.使用同步的集合类:

使用Vector代替ArrayList,使用Hashtable代替HashMap

4.不要在Servlet中创建自己的线程来完成某个功能。

Servlet本身就是多线程的,在Servlet中再创建线程,将导致执行情况复杂化,出现多线程安全问题。

5.在多个servlet中对外部对象(比方文件)进行修改操作一定要加锁,做到互斥的访问。

6.javax.servlet.SingleThreadModel接口是一个标识接口,如果一个Servlet实现了这个接口,那Servlet容器将保证在一个时刻仅有一个线程可以在给定的servlet实例的service方法中执行。将其他所有请求进行排队。

服务器可以使用多个实例来处理请求,代替单个实例的请求排队带来的效益问题。服务器创建一个Servlet类的多个Servlet实例组成的实例池,对于每个请求分配Servlet实例进行响应处理,之后放回到实例池中等待下此请求。这样就造成并发访问的问题。

此时,局部变量(字段)也是安全的,但对于全局变量和共享数据是不安全的,需要进行同步处理。而对于这样多实例的情况SingleThreadModel接口并不能解决并发访问问题。

 

 

 

关于JSP的线程安全

1.采用单线程方式

使用<%page isThreadSafe="false"%>,使得JSP以单线程方式执行.但是这样会降低系统的性能

 

2.对函数加上synchronized关键字.

 

3.采用局部代替实例变量.

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值