55、Java编程:Servlet与事务处理

Java编程:Servlet与事务处理

1. Java事务中的Savepoint

在Java编程里,事务不应仅因为在事务执行时无法发送短信或电子邮件警报就回滚。这时可使用Savepoint将事务回滚到事务中的某个设定点。Savepoint是 java.sql 包中的一个接口,在JDBC 3.0 API中引入。所有到保存点之前所做的更改都会被提交,而保存点之后的更改则会被回滚。以下代码展示了如何回滚到保存点 sp1 ,即回滚 sp1 之后的所有更改,并提交 sp1 之前的所有更改:

try {
    con.setAutoCommit(false); // 
    Statement stmt1 = con.createStatement();
    Statement stmt2 = con.createStatement();
    stmt1.executeUpdate("Query 1");
    Savepoint sp1 = con.setSavepoint("SavePoint1");
    stmt1.executeUpdate("Query 2");
    con.commit();  
    /* commit the changes to the database and make them   
    permanent. */
    ...
} catch(SQLException se) {
    try{
        con.rollback(sp1); // roll back the changes up to the savepoint
    }catch(SQLException){}
}

2. Servlet概述

Servlet是Java服务器端程序,用于接收客户端请求(通常是HTTP请求),处理这些请求并生成响应(通常是HTTP响应)。请求源自客户端的Web浏览器,并被路由到位于合适Web服务器内的Servlet。Servlet在Servlet容器中执行,该容器位于像Apache Tomcat这样的Web服务器中。较新的Tomcat版本还包含JSP(Java服务器页面)容器。通常,Web客户端和Servlet之间使用HTTP(超文本传输协议),但也可以使用其他协议,如FTP(文件传输协议)。

2.1 Servlet的生命周期

Servlet有其自身的执行生命周期,包括以下三个方法,具体流程如下:

graph LR
    A[接收客户端请求] --> B[定位并加载Servlet]
    B --> C[实例化Servlet]
    C --> D[调用init()方法初始化]
    D --> E[调用service()方法处理请求并生成响应]
    E --> F{请求类型}
    F -- get请求 --> G[调用doGet()方法]
    F -- post请求 --> H[调用doPost()方法]
    G --> I[处理完成]
    H --> I[处理完成]
    I --> J[调用destroy()方法进行清理]
  • init() 方法:在Servlet的生命周期中仅被调用一次,用于进行一次性初始化操作。
  • service() 方法:用于处理客户端请求并生成响应。根据HTTP请求的类型,该方法可能会将请求转发给 doGet() doPost() 方法。如果是GET请求,将调用 doGet() 方法;如果是POST请求,将调用 doPost() 方法。 service() 方法能够处理这两种类型的请求,若要自定义处理逻辑,需要在Servlet中重写该方法。该方法接受两个参数: ServletRequest 对象(用于处理客户端请求)和 ServletResponse 对象(用于向客户端写入响应),这两个对象由Servlet容器传递给它。
  • destroy() 方法:在Servlet被卸载之前,由Servlet容器调用,可用于执行清理活动,如关闭数据库连接。

2.2 第一个Servlet示例

下面是一个简单的Servlet示例,用于向客户端输出内容:

import javax.servlet.*; 
import javax.servlet.http.*; 
import java.io.*; 
public class FirstServlet extends HttpServlet { 
    public void service(ServletRequest req, ServletResponse res) throws ServletException,IOException { 
        res.setContentType("text/plain"); 
        PrintWriter pw = res.getWriter(); 
        pw.println("My First Servlet is running");
    }
}
代码解释
  • L1 - L3 :为了创建一个HttpServlet,需要导入 javax.servlet.* javax.servlet.http.* java.io.* 包。
  • L4 FirstServlet 类必须是公共类,并且继承自 HttpServlet ,因为客户端和服务器之间使用HTTP协议进行通信,所以要处理客户端的HTTP请求并生成HTTP响应,就需要创建一个HttpServlet。
  • L5 :重写了 service() 方法,它接受 ServletRequest ServletResponse 两个参数。客户端的整个请求被封装在 ServletRequest 对象(如HTTP、FTP)中,并由Servlet容器与 ServletResponse 对象一起传递给 service() 方法。 ServletResponse 对象用于向客户端发送响应(通常是HTML响应),该方法可能会抛出 ServletException IOException
  • L6 :在向客户端发送任何数据之前,需要使用 res.setContentType("text/plain") 方法指定要发送给客户端的数据类型,即设置MIME类型。在本示例中,Servlet传输的是纯文本,所以MIME类型是 text/plain ;如果要发送HTML(网页),MIME类型则是 text/html
  • L7 - L8 :使用 res 对象的 getWriter() 方法获取一个 PrintWriter 对象,该方法可能会抛出 IOException ,因此在 service() 方法的定义中使用 throws 子句声明了该异常。使用 PrintWriter 对象的 println 方法将内容写入客户端, println 方法的字符串参数会原样写入客户端。

2.3 运行Servlet的步骤

2.3.1 编译Servlet

编译Servlet类时,需要在类路径中包含 servlet-api.jar 文件,可在命令提示符下执行以下命令:

set classpath = %classpath%; C:\Apache\Tomcat 7.0\lib\servlet-api.jar; 

或者编辑环境变量 classpath ,并将上述路径追加到其中。

2.3.2 安装Tomcat 7.0并执行Servlet和JSP

我们使用Tomcat 7.0.40来运行Servlet和JSP。可以从Apache Tomcat网站(如 apache-tomcat-7.0.40 )下载Web服务器安装程序。安装过程非常简单,只需双击安装程序即可开始安装。安装时需要指定Web服务器的安装路径(如果不指定,将安装在计算机的程序文件目录中)。该服务器会作为服务安装在计算机上,可以通过打开计算机的控制面板并点击“管理工具”来手动启动/停止该服务。同时,建议设置以下两个环境变量:
- JAVA_HOME = c:\program files\java\jdk1.6.0_01 (JDK的基本目录)
- CATALINA_HOME = c:\Apache\Tomcat 5.0 (Tomcat的基本目录)

要测试服务器是否实际运行,可打开浏览器,在地址栏中输入 http://localhost:8080

2.3.3 将编译后的Servlet类放置到Tomcat的适当目录

我们在 webapps 目录下创建了名为 myproj 的目录,用于放置创建的Servlet。所有Servlet类文件都放在 classes 目录中, WEB-INF 目录中存在 web.xml 文件。以下是一个运行 FirstServlet.class 的示例 web.xml 文件:

<?xml version = "1.0" encoding = "ISO-8859-1"?>
<!DOCTYPE web-app 
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
    <!-- Define servlets that are included in the example application -->
    <servlet>
        <servlet-name>FirstServlet</servlet-name>
        <servlet-class>FirstServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>FirstServlet</servlet-name>
        <url-pattern>/servlet/FirstServlet</url-pattern>
    </servlet-mapping>
</web-app>

web.xml 文件作为Servlet的部署描述符,它指定了Servlet的名称和URL映射。具体来说,它将Servlet类文件与一个名称关联起来,并为Servlet定义了一个URL映射。每次在 classes 目录中添加新的Servlet类时,都需要编辑该文件,为每个Servlet添加 <servlet> <servlet-mapping> 标签及其子标签,以指定调用Servlet的名称和URL。例如, <servlet> 标签指定 FirstServlet.class 将被称为 FirstServlet <servlet-mapping> 标签指定访问该Servlet的URL为 http://localhost:8080/myproj/servlet/FirstServlet 。在Internet Explorer中运行该Servlet,将显示相应的输出。

2.4 读取客户端数据

客户端数据通过HTTP协议的两种方法(GET和POST)从客户端浏览器发送到服务器。这两种方法在从客户端向服务器发送数据的方式上有所不同:
- GET方法 :将数据附加到处理请求的Servlet的URL后面,并将其传递给服务器。这种方法的缺点是URL的长度是固定的,这限制了可以传输到服务器的数据量,而且发送到服务器的任何数据都以明文形式可见。
- POST方法 :通过将数据作为HTTP头格式的一部分发送到服务器,而不是将其附加到URL,克服了GET方法的这些限制。

以下是用于从客户端请求中获取值的方法:
| 方法 | 描述 |
| — | — |
| String getParameter(String n) | 用于返回与给定名称对应的的值,名称作为参数( String n )指定,“name”是HTML中控件的名称,使用该方法需要知道名称。 |
| Enumeration getParameterNames() | 当我们不总是知道请求中的所有名称时,该方法返回与请求关联的所有名称的枚举。 |
| String[] getParameterValues (String n) | 返回与单个名称关联的所有值的字符串数组,例如爱好。 |

2.4.1 HTML表单示例

以下是一个用于向服务器发送表单数据的HTML文件示例:

<html>
<head><title> form data </title></head>
<body>
    <center>
        <h1><U> Registration Form </u></h1>
        <form method = get action = "servlet/ReadData">
            Name <input type = text name = fname><br>
            Address <input type = text name = add><br>
            User id <input type = text name = uid><br>
            Password <input type = password name = pass><br>
            Gender:
            male<input type = radio name = gender value = male>
            female<input type = radio name = gender value = female><br>
            Hobbies: 
            Dancing <input type = checkbox name = hobbies value = dance>
            Music <input type = checkbox name = hobbies value = music>
            Travel <input type = checkbox name = hobbies value = travel>
            <br>
            <input type = submit>
            <input type = reset>
        </form>
    </center>
</body>
</html>

该HTML页面包含一个表单,表单标签有两个属性: method action method 属性用于指定向服务器发送数据的方法(如GET和POST), action 属性用于指定处理请求的Servlet的URL。表单包含文本字段(如姓名、地址和用户ID)、密码字段、单选按钮和复选框,这些字段通过 input 标签创建, input 标签有 type name 属性, type 指定输入字段的类型, name 属性用于给控件命名,该名称将作为 getParameter(String n) 方法的参数,用于在Servlet中获取其值。以下是 input 标签 type 属性的一些可能值及其描述:
| type 属性值 | 描述 |
| — | — |
| type = text | 创建一个文本字段。 |
| type = submit | 创建一个提交按钮,点击该按钮时,将表单中的数据发送到表单标签 action 属性指定的Servlet,发送方式由 method 属性指定。 |
| type = reset | 创建一个重置按钮,用于重置表单中的所有字段和选择。 |
| type = password | 创建一个密码字段,该字段中的字符以点号显示。 |
| type = button | 创建一个普通按钮,点击该按钮不会有任何动作(可使用JavaScript进行事件处理)。 |
| type = radio | 创建一个单选按钮(单选)。 |
| type = checkbox | 创建一个复选框(多选)。 |

点击提交按钮时,URL将如下所示:

http://localhost:8080/myproj/ReadServlet?fname = peter&add = London&uid = pet_007&pass = jennifer&gender = male&hobbies = music&hobbies = dancing 

? 运算符将数据与地址分隔开, & 运算符将一个名称/值对与另一个分隔开。

2.4.2 提取客户端请求数据的Servlet示例

以下是用于处理客户端请求并生成响应的Servlet示例,该Servlet将所有数据回显给客户端:

import javax.servlet.*; 
import javax.servlet.http.*; 
import java.io.*; 
import java.util.*; 
public class ReadData extends HttpServlet { 
    public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { 
        res.setContentType("text/html"); 
        PrintWriter out = res.getWriter(); 
        Enumeration e = req.getParameterNames(); 
        while(e.hasMoreElements()) { 
            String name = (String)e.nextElement(); 
            String[] values = req.getParameterValues(name); 
            for(int i = 0; i < values.length; i++) { 
                out.println("<html><head><title> client data</title></head>"); 
                out.println("<body><B>"); 
                out.println(name + " : <i>" +values [i] + "</i><br>"); 
                out.println("</body></html>"); 
            } 
        } 
    } 
    public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { 
        doGet(req,res); 
    } 
}
代码解释
  • L1 :除了其他包外,还导入了 java.util 包,原因将在下面的解释中说明。
  • L2 :创建了一个公共的Servlet类 ReadServlet ,该类继承自 HttpServlet ,因为我们处理的是HTTP请求和HTTP响应。
  • L3 :重写了 doGet 方法,用于处理来自客户端的GET请求。该方法接受两个参数: HttpServletRequest (仅用于HTTP客户端请求)和 HttpServletResponse (用于向客户端发送HTTP响应)。与 service 方法类似,该方法也可能抛出 ServletException IOException
  • L6 :通过 req 对象调用 getParameterNames() 方法,该方法从请求对象中的名称/值对中提取所有名称,并将它们作为名称的枚举返回。枚举是一个集合接口,它是 java.util 的一部分,这就是为什么在L1中导入了该包。
  • L7 - L13 :对枚举中的每个名称重复执行以下操作。
  • L8 :从枚举中提取下一个名称,并将其存储在字符串变量 name 中。
  • L9 req.getParameterValues(name) 返回与该名称关联的所有值的字符串数组。
  • L10 - L13 :使用 for 循环遍历数组中的所有值。在 println 方法的引号内写入HTML标签,以将HTML发送到客户端的浏览器。名称以粗体显示,值以粗体和斜体显示。
  • L14 - L15 :重写了 doPost 方法,它几乎与 doGet 方法完全相同。在 doPost 方法中调用 doGet 方法,这样Servlet就能够处理GET和POST请求,无需担心请求是GET请求还是POST请求。

在运行该示例之前,请确保 web.xml 文件中存在Servlet名称和映射, myproj Web应用程序的 web.xml 文件中必须包含以下标签:

<servlet>
    <servlet-name>ReadData</servlet-name>
    <servlet-class>ReadData</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>ReadData</servlet-name>
    <url-pattern>/servlet/ReadData</url-pattern>
</servlet-mapping>

2.5 HTTP重定向

HTTP重定向是一种将用户重定向到Internet上另一个位置的方法。例如,当你的网站有了新的域名,但所有用户都未被告知时,他们仍会访问旧的URL。HTTP重定向可以告知客户端浏览器新的URL,所有到达旧URL的请求都会被重定向到新URL。

3. HTTP重定向的实现方式

在Servlet中实现HTTP重定向通常有两种方式,下面分别进行介绍。

3.1 使用 response.sendRedirect() 方法

这是最常用的实现HTTP重定向的方法,以下是一个简单的示例代码:

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class RedirectServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 设置重定向的URL
        String newUrl = "http://newdomain.com";
        // 执行重定向
        response.sendRedirect(newUrl);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}
代码解释
  • doGet 方法中,首先定义了要重定向的新URL,然后调用 response.sendRedirect(newUrl) 方法将客户端重定向到指定的URL。
  • doPost 方法调用了 doGet 方法,这样该Servlet就能处理GET和POST请求。
配置 web.xml 文件

web.xml 文件中配置该Servlet,示例如下:

<servlet>
    <servlet-name>RedirectServlet</servlet-name>
    <servlet-class>RedirectServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>RedirectServlet</servlet-name>
    <url-pattern>/servlet/RedirectServlet</url-pattern>
</servlet-mapping>

当客户端访问 http://localhost:8080/myproj/servlet/RedirectServlet 时,就会被重定向到 http://newdomain.com

3.2 使用 response.setStatus() response.setHeader() 方法

这种方式相对复杂一些,但可以更灵活地控制重定向的状态码和响应头信息。示例代码如下:

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class CustomRedirectServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 设置重定向的状态码
        response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
        // 设置重定向的URL
        response.setHeader("Location", "http://newdomain.com");
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}
代码解释
  • response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY) 将响应的状态码设置为301,表示永久重定向。
  • response.setHeader("Location", "http://newdomain.com") 设置重定向的目标URL。
配置 web.xml 文件

同样,需要在 web.xml 文件中配置该Servlet:

<servlet>
    <servlet-name>CustomRedirectServlet</servlet-name>
    <servlet-class>CustomRedirectServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>CustomRedirectServlet</servlet-name>
    <url-pattern>/servlet/CustomRedirectServlet</url-pattern>
</servlet-mapping>

3.3 两种方式的比较

方式 优点 缺点
response.sendRedirect() 代码简单,使用方便,自动处理状态码和响应头信息 只能进行302临时重定向,无法灵活控制状态码
response.setStatus() response.setHeader() 可以灵活控制重定向的状态码和响应头信息 代码相对复杂,需要手动设置状态码和响应头

4. Cookie的使用

Cookie是在客户端浏览器和服务器之间传递的小段数据,用于存储用户的信息,如用户偏好、登录状态等。以下是一个简单的Cookie使用示例。

4.1 创建和发送Cookie

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class CookieDemo extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 创建一个Cookie对象
        Cookie cookie = new Cookie("username", "JohnDoe");
        // 设置Cookie的有效期(单位:秒)
        cookie.setMaxAge(3600);
        // 将Cookie添加到响应中
        response.addCookie(cookie);
        // 设置响应内容类型
        response.setContentType("text/html");
        // 获取输出流
        java.io.PrintWriter out = response.getWriter();
        // 输出提示信息
        out.println("<html><body>");
        out.println("Cookie has been set.");
        out.println("</body></html>");
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}
代码解释
  • Cookie cookie = new Cookie("username", "JohnDoe") 创建了一个名为 username ,值为 JohnDoe 的Cookie对象。
  • cookie.setMaxAge(3600) 设置Cookie的有效期为1小时(3600秒)。
  • response.addCookie(cookie) 将Cookie添加到响应中,发送给客户端。
配置 web.xml 文件
<servlet>
    <servlet-name>CookieDemo</servlet-name>
    <servlet-class>CookieDemo</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>CookieDemo</servlet-name>
    <url-pattern>/servlet/CookieDemo</url-pattern>
</servlet-mapping>

4.2 读取Cookie

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class ReadCookie extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取客户端发送的所有Cookie
        Cookie[] cookies = request.getCookies();
        // 设置响应内容类型
        response.setContentType("text/html");
        // 获取输出流
        java.io.PrintWriter out = response.getWriter();
        // 输出HTML头部
        out.println("<html><body>");
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("username".equals(cookie.getName())) {
                    out.println("Username: " + cookie.getValue());
                }
            }
        } else {
            out.println("No cookies found.");
        }
        // 输出HTML尾部
        out.println("</body></html>");
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}
代码解释
  • Cookie[] cookies = request.getCookies() 获取客户端发送的所有Cookie。
  • 通过遍历 cookies 数组,找到名为 username 的Cookie,并输出其值。
配置 web.xml 文件
<servlet>
    <servlet-name>ReadCookie</servlet-name>
    <servlet-class>ReadCookie</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>ReadCookie</servlet-name>
    <url-pattern>/servlet/ReadCookie</url-pattern>
</servlet-mapping>

4.3 删除Cookie

要删除一个Cookie,可以将其有效期设置为0,示例代码如下:

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class DeleteCookie extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取客户端发送的所有Cookie
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("username".equals(cookie.getName())) {
                    // 设置Cookie的有效期为0
                    cookie.setMaxAge(0);
                    // 将修改后的Cookie添加到响应中
                    response.addCookie(cookie);
                }
            }
        }
        // 设置响应内容类型
        response.setContentType("text/html");
        // 获取输出流
        java.io.PrintWriter out = response.getWriter();
        // 输出提示信息
        out.println("<html><body>");
        out.println("Cookie has been deleted.");
        out.println("</body></html>");
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}
配置 web.xml 文件
<servlet>
    <servlet-name>DeleteCookie</servlet-name>
    <servlet-class>DeleteCookie</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>DeleteCookie</servlet-name>
    <url-pattern>/servlet/DeleteCookie</url-pattern>
</servlet-mapping>

5. 总结

本文详细介绍了Java编程中Servlet的相关知识,包括事务处理中的Savepoint、Servlet的生命周期、创建和运行Servlet的步骤、读取客户端数据、HTTP重定向以及Cookie的使用等内容。通过实际的代码示例和详细的解释,帮助读者更好地理解和掌握这些技术。

5.1 关键知识点回顾

  • Savepoint :用于在事务中回滚到指定的保存点,避免因某些操作失败而导致整个事务回滚。
  • Servlet生命周期 :包括 init() service() destroy() 方法,分别用于初始化、处理请求和清理资源。
  • 运行Servlet步骤 :包括编译Servlet、安装Tomcat、配置 web.xml 文件等。
  • 读取客户端数据 :通过 getParameter() getParameterNames() getParameterValues() 方法获取客户端请求中的数据。
  • HTTP重定向 :可以使用 response.sendRedirect() response.setStatus() response.setHeader() 方法实现。
  • Cookie使用 :包括创建、读取和删除Cookie,用于在客户端和服务器之间传递数据。

5.2 后续学习建议

  • 深入学习Servlet的高级特性,如过滤器、监听器等。
  • 结合数据库操作,实现更复杂的Web应用程序。
  • 学习JSP(Java Server Pages)技术,提高页面开发效率。

希望本文能为读者在Java Web开发方面提供有价值的参考,帮助大家更好地掌握Servlet相关知识和技能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值