網站在注冊新用戶過程中,需要驗證很多內容。例如,用戶名是否已存在,E-mail是否已被人使用,驗證碼是否正確等。傳統方式是使用客戶端JavaScript做初步驗證,用戶提交表單后在服務器端做進一步驗證。如果用戶輸入的內容有錯誤,會返回注冊頁面,提示用戶修改。使用了Ajax技術后,很多原來必須提交到服務器才能驗證的內容,可以在不刷新頁面的情況下直接驗證。本例就演示了這個過程,實例運行效果如圖3.1所示。
3.1.1 技術要點
本例中要驗證的內容有用戶名、密碼、E-mail和驗證碼4部分內容。但從技術實現上主要有3種形式,下面來一一介紹。
1.驗證用戶名和E-mail是否已存在
在用戶輸入用戶名或E-mail之后,使用XMLHttpRequest對象將用戶輸入的信息發送給服務器。服務器判斷是否存在同名用戶或E-mail地址。驗證完畢后將信息反饋給客戶端,由客戶端顯示驗證結果。這樣用戶在未提交整個表單前,就可以知道輸入的用戶名或E-mail是否可以使用。
2.密碼驗證
密碼驗證比較簡單,不需要到服務器判斷。只需在客戶端對兩次輸入的密碼進行比對,當輸入一致時通過驗證,否則提示用戶密碼有誤。
3.生成驗證碼與校驗過程
驗證碼主要是防止惡意用戶使用工具自動進行批量注冊,搶占用戶名。其基本原理是在服務器生成一個隨機數字,并放入用戶session中。客戶端顯示使用該隨機數字生成的圖片,用戶按照圖片內容輸入驗證碼。最后將用戶的輸入與session中的驗證碼進行比對,如果一致則驗證成功。本例中使用Java類庫中圖像API生成包含三位數字驗證碼的PNG格式圖片。具體的代碼可參考code.jsp。
4.將驗證函數封裝在Checker對象中
以上3種方式的驗證函數都封裝在一個Checker對象中。里面包含的checkNode函數對應用戶名和E-mail驗證,以及驗證碼校驗。checkPassword函數對應密碼驗證。所有的驗證結果第一個字符是數字0或1,分別表示驗證失敗或成功。后面緊跟驗證結果的詳細文字說明。showInfo函數根據驗證結果進行不同樣式的顯示。
3.1.2 數據庫設計
本實例使用名為users的數據庫表,包含的數據如圖3.2所示。具體的創建數據表語句如下:
CREATE TABLE 'users' (
'id' int(11) NOT NULL auto_increment,
'username' varchar(255) NOT NULL,
'password' varchar(255) NOT NULL,
'E-mail' varchar(255) NOT NULL,
PRIMARY KEY ('id')
)

圖3.2 表users包含的數據
本實例共包括4個文件:用戶操作界面register.html,服務器端響應文件checker.jsp,驗證碼圖片生成文件code.jsp,以及JavaScript文件checker.js。
3.1.3 用戶操作界面register.html
頁面包含操作界面樣式以及注冊表單。表單中各元素通過onblur(失去焦點)事件觸發驗證函數。每個表單元素對應一個div,用于顯示驗證結果。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>注冊表單驗證</title>
<style type="text/css">
/* 頁面字體樣式 */
body, td, input {
font-family:Arial;
font-size:12px;
}
/* 表格基本樣式 */
table.default {
border-collapse:collapse;
width:300px;
}
/* 表格單元格樣式 */
table.default td {
border:1px solid black;
padding:3px;
}
/* 列頭樣式 */
table.default td.item {
background:#006699;
color:#fff;
}
/* 正常信息樣式 */
div.ok {
color:#006600;
}
/* 警告信息樣式 */
div.warning {
color:#FF0000;
}
</style>
<!-- 引入外部checker.js文件 -->
<script type="text/javascript" src="checker.js"></script>
</head>
<body>
<h1>注冊表單驗證</h1>
<form method="post" action="register.jsp"
onsubmit="alert('后面的注冊過程與傳統方式相同。');return false">
<table class="default">
<tr>
<td class="item" width="30%">用戶名:</td>
<td width="70%">
<input type="text" name="username" id="username" onblur="Checker.checkNode(this)">
<div id="usernameCheckDiv" class="warning">請輸入用戶名。</div>
</td>
</tr>
<tr>
<td class="item">密碼:</td>
<td>
<input type="password" name="password" id="password" onblur="Checker.checkPassword()">
<div id="passwordCheckDiv" class="warning">請輸入密碼。</div>
</td>
</tr>
<tr>
<td class="item">密碼驗證:</td>
<td>
<input type="password" name="password2" id="password2" onblur="Checker.checkPassword()">
<div id="password2CheckDiv" class="warning">請再次輸入密碼。</div>
</td>
</tr>
<tr>
<td class="item">E-mail:</td>
<td>
<input type="text" name="E-mail" id="E-mail" onblur="Checker.checkNode(this)">
<div id="E-mailCheckDiv" class="warning">請輸入郵件地址。</div>
</td>
</tr>
<tr>
<td class="item">驗證碼:</td>
<td>
<input type="text" name="code" id="code" size="5"
onblur="Checker.checkNode(this)">
<img src="code.jsp" width="40" height="20" border="0" alt="">
<div id="codeCheckDiv" class="warning">請輸入圖片中的數字驗證碼。</div>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" value="注冊">
</td>
</tr>
</table>
</form>
</body>
</html>
3.1.4 服務器端響應文件checker.jsp
服務器端響應文件主要包含兩類驗證,根據用戶提交的表單元素名確定驗證方式。如果表單元素名是userName或E-mail,則進行數據查詢,判斷是否存在相同信息。如果是code,則直接通過對比session中的驗證碼進行判斷。
<%@ page contentType="text/plain; charset=UTF-8"%>
<%@ page language="java"%>
<%@ page import="java.sql.*,ajax.db.DBUtils"%>
<%!
//查詢數據庫是否存在相同信息
boolean hasSameValue(String name, String value) {
boolean result = false; //保存檢測結果
//定義查詢數據庫的SQL語句
String sql = "select * from users where " + name + " = ?";
Connection conn = null; //聲明Connection對象
PreparedStatement pstmt = null; //聲明PreparedStatement對象
ResultSet rs = null; //聲明ResultSet對象
try {
conn = DBUtils.getConnection(); //獲取數據庫連接
pstmt = conn.prepareStatement(sql); //根據sql創建PreparedStatement
pstmt.setString(1, value); //設置參數
rs = pstmt.executeQuery(); //執行查詢,返回結果集
//根據結果集是否存在決定查詢結果
if (rs.next()) {
result = true;
} else {
result = false;
}
} catch (SQLException e) {
System.out.println(e.toString());
} finally {
DBUtils.close(rs); //關閉結果集
DBUtils.close(pstmt); //關閉PreparedStatement
DBUtils.close(conn); //關閉連接
}
return result;
}
%>
<%
out.clear(); //清空當前的輸出內容(空格和換行符)
request.setCharacterEncoding("UTF-8"); //設置請求體字符編碼格式為UTF-8
String name = request.getParameter("name"); //獲取name參數
String value = request.getParameter("value"); //獲取value參數
String info = null; //用于保存提示對象的名稱
//如果需要判斷的是驗證碼,采用session方式驗證
if ("code".equals(name)) {
//獲取session中保存的驗證碼
String sessionCode = (String) session.getAttribute("_CODE_");
//根據對比結果輸出響應信息
if (value != null && value.equals(sessionCode)) {
out.print("1驗證碼正確。");
} else {
out.print("0驗證碼錯誤。");
}
} else {
//根據name變量確定提示對象的名稱
if ("username".equals(name)) {
info = "用戶名";
} else if ("E-mail".equals(name)) {
info = "郵件地址";
}
//根據是否存在相同信息輸出對應的響應
if (hasSameValue(name, value)) {
out.print("0該" + info + "已存在,請更換" + info + "。");
} else {
out.print("1該" + info + "可正常使用。");
}
}
%>
3.1.5 驗證碼生成文件code.jsp
生成驗證碼要注意頁首contentType的設置為image/png。主要使用了awt組件和imageio組件中相關的API進行圖片生成,隨機數是使用java.util.Random類生成的。詳細的過程參考以下代碼:
<%@ page contentType="image/png" import="java.awt.*,java.awt.image.*,java.util.*,javax.imageio.*" %><%
//設置頁面不緩存
response.setHeader("Pragma","No-cache");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", 0);
int width=40; //設置圖片寬度
int height=20; //設置圖片高度
//創建緩存圖像
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics(); //獲取圖形
g.setColor(new Color(000, 102, 153)); //設置背景色
g.fillRect(0, 0, width, height); //填充背景
g.setColor(new Color(000, 000, 000)); //設置邊框顏色
g.drawRect(0, 0, width-1, height-1); //繪制邊框
g.setFont(new Font("Arial", Font.PLAIN, 16)); //設定字體
Random random = new Random(); //生成隨機類
//隨機產生3位數字驗證碼
StringBuffer sbRan = new StringBuffer(); //保存驗證碼文本
for (int i=0; i<3; i++){
String ranNum = String.valueOf(random.nextInt(10));
sbRan.append(ranNum);
//將驗證碼繪制到圖像中
g.setColor(new Color(255, 255, 255));
g.drawString(ranNum, 10 * i + 5, 16);
}
g.dispose(); //部署圖像
session.setAttribute("_CODE_", sbRan.toString()); //將驗證碼保存在session對象中供對比
ImageIO.write(image, "PNG", response.getOutputStream()); //輸出圖像到頁面
%>
3.1.6 JavaScript文件checker.js
所有的驗證函數都封裝在一個名為Checker的對象中,獨立存在于checker.js文件里。函數調用流程如圖3.3所示。

圖3.3 函數調用流程
var Checker = new function() {
this._url = "checker.jsp"; //服務器端文件地址
this._infoDivSuffix = "CheckDiv"; //提示信息div的統一后綴
//檢查普通輸入信息
this.checkNode = function(_node) {
var nodeId = _node.id; //獲取節點id
if (_node.value!="") {
var xmlHttp=this.createXmlHttp(); //創建XmlHttpRequest對象
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4) {
//調用showInfo方法顯示服務器反饋信息
Checker.showInfo(nodeId + Checker._infoDivSuffix,
xmlHttp.responseText);
}
}
xmlHttp.open("POST", this._url, true);
xmlHttp.setRequestHeader(
"Content-type","application/x-www-form-urlencoded");
xmlHttp.send("name=" + encodeURIComponent(_node.id) +
//發送包含用戶輸入信息的請求體
"&value=" + encodeURIComponent(_node.value));
}
}
//顯示服務器反饋信息
this.showInfo = function(_infoDivId, text) {
var infoDiv = document.getElementById(_infoDivId); //獲取顯示信息的div
var status = text.substr(0,1); //反饋信息的第一個字符表示信息類型
if (status == "1") {
infoDiv.className = "ok"; //檢查結果正常
} else {
infoDiv.className = "warning"; //檢查結果需要用戶修改
}
infoDiv.innerHTML = text.substr(1); //寫回詳細信息
}
//用于創建XMLHttpRequest對象
this.createXmlHttp = function() {
var xmlHttp = null;
//根據window.XMLHttpRequest對象是否存在使用不同的創建方式
if (window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest(); //FireFox、Opera等瀏覽器支持的創建方式
} else {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");//IE瀏覽器支持的創建方式
}
return xmlHttp;
}
//檢查兩次輸入的密碼是否一致
this.checkPassword = function() {
var p1 = document.getElementById("password").value; //獲取密碼
var p2 = document.getElementById("password2").value; //獲取驗證密碼
//當兩部分密碼都輸入完畢后進行判斷
if (p1 != "" && p2 != "") {
if (p1 != p2) {
this.showInfo("password2" + Checker._infoDivSuffix,
"0密碼驗證與密碼不一致。");
} else {
this.showInfo("password2" + Checker._infoDivSuffix, "1");
}
} else if (p1 != null) {
this.showInfo("password" + Checker._infoDivSuffix, "1");
}
}
}
3.1.7 小結
表單驗證在網站中是很常見的內容,可以說只要是用到表單的地方,十有八九需要進行內容驗證。當遇到需要提交給服務器才能驗證的字段時,使用Ajax技術是一個非常好的選擇。本例雖然只講解了用戶注冊的表單驗證,但其基本思想和實現技術可以推廣到各種類型的表單驗證中去。