网站在注册新用户过程中,需要验证很多内容。例如,用户名是否已存在,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技术是一个非常好的选择。本例虽然只讲解了用户注册的表单验证,但其基本思想和实现技术可以推广到各种类型的表单验证中去。