开发说明和数据库准备
- 对于任何的系统而言,用户的注册操作一定是最为常用的功能,在整个注册之中用户名不能够重复,而且防止出现意外情况,都要求使用验证码验证.
- 对于验证码的操作,在使用JSP的时候,是将请求处理,将输入的验证码交给了服务器操作,在服务器端上进行判断,这昂的操作一定会家中服务器负担,所以最好的做法是将验证码交给客户端执行,同时对于,用户名的重复检查处理操作也应该在客户端完成验证.
- 本次使用MySQL数据库
- 使用MD5加密用户密码
- 准备数据库脚本
-- 删除数据库
DROP DATABASE IF EXISTS ajaxdb;
--创建新的数据库
CREATE DATABASE ajaxdb CHARACTER SET UTF-8;
--使用数据库
USE ajaxdb;
--删除原有的数据表
DROP TABLE IF EXISTS member;
--创建新的数据表
CREATE TABLE member(
mid VARCHAR(50),
password VARCHAR(32),
CONSTRAINT pk_mid PRIMARY KEY(mid)
);
- 创建测试数据
-- admin/hello
INSERT INTO member(mid,password)VALUES('admin','E2F033D13CFA9F3E6146F60A4D9A2057');
-- mldn/java
INSERT INTO member(mid,password)VALUES('mldn','958FCEFC8E24C8CDA7371F8781A0D1ED');
- 加密的原数据格式为"密码{用户名}"
开发后台业务层
- 后台程序操作主要以业务层,及数据层为主
- 定义DatabaseConnection程序类,建立数据库连接类对象
package mao.shu.dbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* @writer Maoshu
* @date 2019年1月6日21:07:01
*进行本地mysql数据库的连接,使用root用户<br/>
* getConnection()方法,可以得到一个连接了mysql数据库的连接对象<br/>
* close()方法可以关闭当前连接的数据库<br/>
*/
public class DatabaseConnection {
//数据库加载驱动程序类
private static final String DBDRIVER = "org.gjt.mm.mysql.Driver";
//数据库连接路径
private static final String DBURL = "jdbc:mysql://localhost:3306/ajaxdb";
//数据库连接的用户名
private static final String USER = "root";
//连接数据库的密码
private static final String PASSWORD = "mysqladmin";
//保存数据库连接对象
private Connection conn;
public DatabaseConnection(){
try{
//加载驱动程序类
Class.forName(DBDRIVER);
this.conn = DriverManager.getConnection(DBURL,USER,PASSWORD);
}catch(Exception e){
e.printStackTrace();
}
}
public Connection getConnection(){
if(this.conn != null){
return this.conn;
}
return null;
}
public void close(){
if(this.conn != null){
try {
this.conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
- 定义Member的Vo类
package mao.shu.vo;
import java.io.Serializable;
public class Member implements Serializable {
private String mid;
private String password;
public String getMid() {
return mid;
}
public String getPassword() {
return password;
}
public void setMid(String mid) {
this.mid = mid;
}
public void setPassword(String password) {
this.password = password;
}
}
- 定义IMemberDAO接口,此时定义两个方法,
- 进行同名账户检查
package mao.shu.dao;
import mao.shu.vo.Member;
import java.sql.SQLException;
public interface IMemberDAO {
/**
* 根据指定的mid查询用户
* @return
* @throws SQLException
*/
public Member findById(String mid)throws SQLException;
/**
* 保存新的用户信息
* @param vo
* @return
* @throws Exception
*/
public boolean create(Member vo) throws Exception;
}
- 定义IMemberDAO接口的子类
package mao.shu.dao.imple;
import mao.shu.dao.IMemberDAO;
import mao.shu.vo.Member;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class MemberDAOImpl implements IMemberDAO {
private Connection conn;
private PreparedStatement pstmt;
public MemberDAOImpl(Connection conn){
this.conn = conn;
}
@Override
public Member findById(String mid) throws SQLException {
String sql = "SELECT mid,password FROM member WHERE mid=?";
this.pstmt = this.conn.prepareStatement(sql);
this.pstmt.setString(1,mid);
ResultSet rs = this.pstmt.executeQuery();
if(rs.next() ){
Member vo = new Member();
vo.setMid(rs.getString(1));
vo.setPassword(rs.getString(2));
return vo;
}
return null;
}
@Override
public boolean create(Member vo) throws Exception {
String sql = "INSERT INTO member(mid,password)VALUES(?,?)";
this.pstmt = this.conn.prepareStatement(sql);
this.pstmt.setString(1,vo.getMid());
this.pstmt.setString(2,vo.getPassword());
return this.pstmt.executeUpdate() > 0;
}
}
- 创建DAOFactory工程类
package mao.shu.factory;
import mao.shu.dao.IMemberDAO;
import mao.shu.dao.imple.MemberDAOImpl;
import java.sql.Connection;
public class DAOFactory {
public static IMemberDAO newMemberDAO(Connection conn){
return new MemberDAOImpl(conn);
}
}
- 定义IMemberService’接口,需要两个方法,一个是进行用户名的检查,一个是用户的创建
package mao.shu.service;
import mao.shu.vo.Member;
public interface IMemberService {
/**
* 检查该用户是否存在
* @param mid
* @return
*/
public boolean checkMid(String mid);
/**
* 创建新的用户
* @param vo 包含新用户新
* @return 返回是否创建成功
*/
public boolean register(Member vo);
}
- 当vo数据提交到业务层之后,里面的内容一定是正确的内容
- 定义IMemberService的实现子类
package mao.shu.service.impl;
import mao.shu.dao.IMemberDAO;
import mao.shu.dbc.DatabaseConnection;
import mao.shu.factory.DAOFactory;
import mao.shu.service.IMemberService;
import mao.shu.vo.Member;
import java.sql.Connection;
public class MemberService implements IMemberService {
private DatabaseConnection dataC = new DatabaseConnection();
@Override
public boolean checkMid(String mid) {
try{
IMemberDAO memberDAO = DAOFactory.newMemberDAO(this.dataC.getConnection());
return memberDAO.findById(mid) != null;
}catch(Exception e){
e.printStackTrace();
}finally{
if(this.dataC != null){
this.dataC.close();
}
}
return false;
}
@Override
public boolean register(Member vo) {
try{
IMemberDAO memberDAO = DAOFactory.newMemberDAO(this.dataC.getConnection());
if(memberDAO.findById(vo.getMid())==null) {
return memberDAO.create(vo);
}
}catch(Exception e){
e.printStackTrace();
}finally{
if(this.dataC != null){
this.dataC.close();
}
}
return false;
}
}
- 定义业务层的工厂类
package mao.shu.factory;
import mao.shu.service.IMemberService;
import mao.shu.service.impl.MemberServiceImpl;
public class ServiceFactory {
public static IMemberService getMemberService(){
return new MemberServiceImpl();
}
}
注册页面实现与基本验证
- 定义一个MemberServlet,路径设置为"/MemberServlet/*"
package mao.shu.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/MemberServlet/*")
public class MemberServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
}
- 定一个member.html页面(暂时将JavaScript代码写在HTML页面之中)
- 定义用户的注册页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Ajax异步处理交互</title>
</head>
<body>
<form action="MemberServlet/register">
用户名:<input type="text" id="mid" name="mid"/><span id="midSpan"></span><br/>
密 码:<input type="password" id="password" name="password"/><span id="passwordSpan"></span> <br/>
确认密码:<input type="password" id="cong" name="cong"/><span id="congSpan"></span> <br/>
验证码:<input type="text" id="validate" name="validate"/>
<img src="ValidateCode" alt="">
<span id="validateSpan"></span>
<br/>
<input type="submit" id="submit"/>
<input type="reset" id="reset"/>
</form>
</body>
</html>
- 既然都有表单了,那么一定要馊现进行数据的基本验证.
- 在member.html文件中添加javascript代码,进行基础表单验证(重复代码太多)
<script type="text/javascript">
//保存XMLHttpRequest对象的变量
var xmlHttpRequest;
window.onload=function(){
document.getElementById("mid").addEventListener("blur",function(){
validateMid();
},false)
document.getElementById("password").addEventListener("blur",function(){
validatePassword();
},false)
document.getElementById("cong").addEventListener("blur",function(){
validateCong();
},false)
document.getElementById("validate").addEventListener("blur",function(){
validateCode();
},false)
}
function validateMid(){
var midEle = document.getElementById("mid");
//得到提元素
var hintEle = document.getElementById("midSpan");
var str = midEle.value;
if(str == ""){
hintEle.innerHTML = "<font color='red'>用户名不能为空</font>";
return false;
}else{
hintEle.innerHTML = "<font color='green'>√</font>";
return true;
}
}
function validatePassword(){
var passwordEle = document.getElementById("password");
//得到提元素
var hintEle = document.getElementById("passwordSpan");
var str = passwordEle.value;
if(str == ""){
hintEle.innerHTML = "<font color='red'>密码不能为空</font>";
}else{
hintEle.innerHTML = "<font color='green'>√</font>";
return true;
}
return false;
}
function validateCong(){
//得到提元素
var hintEle = document.getElementById("congSpan");
var passwordEle = document.getElementById("password");
var passstr = passwordEle.value;
var congEle = document.getElementById("cong");
var congstr = congEle.value;
if( congstr != passstr || congstr == ""){
hintEle.innerHTML = "<font color='red'>两次密码输入不一致</font>";
}else{
hintEle.innerHTML = "<font color='green'>√</font>";
return true;
}
return false;
}
function validateCode(){
var str = document.getElementById("validate").value;
var validateSpanEle = document.getElementById("validateSpan")
if(str == ""){
validateSpanEle.innerHTML = "<font color='red'>验证码错误</font>";
}else{
validateSpanEle.innerHTML = "<font color='green'>√</font>";
return true;
}
return false;
}
</script>
- member.html页面的表单效果
- 生成验证码的Servlet
package mao.shu.servlet;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
/**
* @writer Maoshu
* @date 2019年1月7日12:59:00
* @version 0.1
* 生成随机字符的验证码<br/>
* 使用Random类生成随机数<br/>
* 使用字符数组保存验证字符<br/>
* 使用BufferedImage保存图片<br/>
* 使用Graphics类对象绘制图片<br/>
* 使用ImageIO将图片输出到网页中<br/>
* 使用session保存验证码</br/>
*/
@WebServlet("/ValidateCode")
public class ValidateCode extends HttpServlet {
private static final Random random = new Random();
//保存生成的随机字符
private static char[] datasChar = "ABCDEFGHIJKLMNOPQISTUVWXYZabcdefghijklmnopqistuvwxyz0123456789".toCharArray();
//生成的验证码中的字符个数
private static final int CHAR_NUMBER = 4;
//验证码图片的宽度
private static final int IMAGE_WIDE = 65;
//验证码图片的高度
private static final int IMAGE_HEIGHT = 25;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//创建图形
BufferedImage bufferedImage = new BufferedImage(IMAGE_WIDE,IMAGE_HEIGHT,BufferedImage.TYPE_INT_RGB);
//绘图类
Graphics graphics = bufferedImage.createGraphics();
//创建背景颜色
Color color = new Color(200,222,232);
//设置图片背景
graphics.setColor(color);
// 创建验证码图片边框
graphics.fillRect(0,0,IMAGE_WIDE,IMAGE_HEIGHT);
//保存验证码字符
StringBuffer codeChar = new StringBuffer();
//随机在dataChar[] 数组中随机生成四个字符
int index;
for(int x = 0; x < CHAR_NUMBER; x++){
index = random.nextInt(datasChar.length);
graphics.setColor(new Color(random.nextInt(88),random.nextInt(150),random.nextInt(250)));
//在图形中绘制字符
graphics.drawString(datasChar[index]+"",15*x+3,18);
//拼接四个字符
codeChar.append(datasChar[index]);
}
//将验证码数据保存在Session中
req.getSession().setAttribute("validateCode",codeChar.toString());
//将图片输出到网页中
ImageIO.write(bufferedImage,"jpg",resp.getOutputStream());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
}
验证码检测处理
验证码的检查和业务层没有任何关系,所以现在直接建立一个验证码检车的Serlvet,这个Servlet就判断 验证码是否正确
- 建立一个CheckRandomServlet
package mao.shu.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/CheckValidateCodeServlet")
public class CheckValidateCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//得到用户提交的验证码信息
String childCode = req.getParameter("validateCode");
//的到页面随机生成的验证码数据
String randomCode = (String)req.getSession().getAttribute("validateCode");
if(childCode != null || !"".equals(childCode)){
resp.getWriter().print(childCode.equalsIgnoreCase(randomCode));
}else{
resp.getWriter().print(false);
}
}
}
- 接下来需要进行前台页面的检查了,并且考虑到合理性的问题,对于前台检查处理,一定要在验证码输入后执行
- 扩充validatecode()函数
function validateCode(){
var str = document.getElementById("validate").value;
var validateSpanEle = document.getElementById("validateSpan");
if(str == ""){
validateSpanEle.innerHTML = "<font color='red'>验证码不允许为空</font>";
}else{
//此时表示用户有输入验证码
//使用AJAX异步处理
createXMLHttpRequest();
//设置AJAX请求路径和类型
xmlHttpRequest.open("post","CheckValidateCodeServlet?validateCode="+str);
//发送Ajax请求
xmlHttpRequest.send(null);
//处理Ajax发送请求状态改变事件
xmlHttpRequest.onreadystatechange = function(){
//如果当Ajax请求发送完成
if(xmlHttpRequest.readyState == 4){
//当Ajax请求处理结果返回为 200 表示成功了
alert("true" == "true" +
"");
if(xmlHttpRequest.status == 200){
if(xmlHttpRequest.responseText == "true"){
validateSpanEle.innerHTML = "<font color='green'>验证码输入成功</font>";
}else{
validateSpanEle.innerHTML = "<font color='red'>验证码输入错误</font>";
}
}else{
validateSpanEle.innerHTML = "<font color='red'>验证码输入错误</font>";
}
}
}
}
}
- 虽然以上的函数实现了基础的验证,但是会出现几个问题
- 如果第一次验证码输入错误了,那应该将原本的验证码刷新
- 首先需要在验证码图片位置上加一个"id"
<img src="ValidateCode" id="validateImg"/>
- 为此图片绑定一个单机事件
document.getElementById("validateImg").addEventListener("click",function(){
refreshValidateImg();
},false)
function refreshValidateImg(){
//得到验证码图片元素
var imgEle = document.getElementById("validateImg");
//改变图片的地址,重新生成一个验证码,但是需要改变路径,这样JavaScript才会认为需要重新加载页面
imgEle.src = "ValidateCode?p="+Math.random();
}
- 当用户输入验证码出现错误的时候,执行refreshValidateImg()函数,重新生成验证码
function validateCode(){
var str = document.getElementById("validate").value;
var validateSpanEle = document.getElementById("validateSpan");
if(str == ""){
validateSpanEle.innerHTML = "<font color='red'>验证码不允许为空</font>";
}else{
//此时表示用户有输入验证码
//使用AJAX异步处理
createXMLHttpRequest();
//设置AJAX请求路径和类型
xmlHttpRequest.open("post","CheckValidateCodeServlet?validateCode="+str);
//发送Ajax请求
xmlHttpRequest.send(null);
//处理Ajax发送请求状态改变事件
xmlHttpRequest.onreadystatechange = function(){
//如果当Ajax请求发送完成
if(xmlHttpRequest.readyState == 4){
//当Ajax请求处理结果返回为 200 表示成功了
if(xmlHttpRequest.status == 200){
if(xmlHttpRequest.responseText == "true"){
validateSpanEle.innerHTML = "<font color='green'>验证码输入成功</font>";
}else{
validateSpanEle.innerHTML = "<font color='red'>验证码输入错误</font>";
refreshValidateImg();
}
}else{
validateSpanEle.innerHTML = "<font color='red'>验证码输入错误</font>";
refreshValidateImg();
}
}
}
}
}
- 问题二:在整个表单验证的操作的时候无法使用异步验证
- 将异步验证的结果,用一个变量表示,然后再进行整个表单验证的时候,使用这个变量判断验证码判断的结果.
- 为表单绑定提交事件函数
//保存整个表单验证是否通过的标志
var formCheckBoolean = false;
//为表单绑定submit提交事件函数
document.getElementById("userAdd").addEventListener("submit",function(e){
if(validateForm()){
this.submit();
}else {
//进行填单阻拦
//ie浏览器
if(e && e.preventDefault){
e.preventDefault();
}else{
//其他浏览器阻拦
window.event.returnValue = false;
}
}
},false)
function validateForm(){
return validateMid() && validatePassword() && validateCong() && formCheckBoolean;
}
//修改
function validateCode(){
var str = document.getElementById("validate").value;
var validateSpanEle = document.getElementById("validateSpan");
if(str == ""){
validateSpanEle.innerHTML = "<font color='red'>验证码不允许为空</font>";
}else{
//此时表示用户有输入验证码
//使用AJAX异步处理
createXMLHttpRequest();
//设置AJAX请求路径和类型
xmlHttpRequest.open("post","CheckValidateCodeServlet?validateCode="+str);
//发送Ajax请求
xmlHttpRequest.send(null);
//处理Ajax发送请求状态改变事件
xmlHttpRequest.onreadystatechange = function(){
//如果当Ajax请求发送完成
if(xmlHttpRequest.readyState == 4){
//当Ajax请求处理结果返回为 200 表示成功了
if(xmlHttpRequest.status == 200){
if(xmlHttpRequest.responseText == "true"){
//将验证码成功标志改为 "true"
formCheckBoolean = true;
validateSpanEle.innerHTML = "<font color='green'>验证码输入成功</font>";
}else{
validateSpanEle.innerHTML = "<font color='red'>验证码输入错误</font>";
refreshValidateImg();
}
}else{
validateSpanEle.innerHTML = "<font color='red'>验证码输入错误</font>";
refreshValidateImg();
}
}
}
}
}
- 整体效果
用户名重复检测
在进行注册检查的时候,必须保证用户输入的用户名没有重复
- 在MemberServlet程序中定义一个查询处理,这个处理是处理用户名是否存在的判断
- 在MemberServlet中,通过判断请求路径/"符号后面的状态字符,来区分要调用哪个方法
- 在通过反射的方式执行方法.
package mao.shu.servlet;
import mao.shu.factory.ServiceFactory;
import mao.shu.service.IMemberService;
import mao.shu.util.MD5Util;
import mao.shu.vo.Member;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
@WebServlet("/MemberServlet/*")
public class MemberServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
//得到路径信息
String uri = req.getRequestURI();
//得到此时提交的状态
String status = uri.substring(uri.lastIndexOf("/")+1);
//通过反射得到要处理的方法
if(status != null){
try {
Method method = this.getClass().getMethod(status,HttpServletRequest.class,HttpServletResponse.class);
method.invoke(this,req,resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
//增加新的用户
public void register(HttpServletRequest request,HttpServletResponse response){
//得到用户提交的信息
String mid = request.getParameter("mid");
String password = request.getParameter("password");
if(mid == null && password == null && "".equals(mid) && "".equals(password)){
try {
response.getWriter().print("User creation failed");
} catch (IOException e) {
e.printStackTrace();
}
}else{
//的到业务层操作接口对象
IMemberService memberService = ServiceFactory.getMemberService();
Member vo = new Member();
vo.setMid(mid);
vo.setPassword(new MD5Util().getMD5ofStr(password+"{"+mid+"}"));
memberService.register(vo);
try {
response.getWriter().print("User created successfully!!!!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 判断用户id是否存在
*/
public void checkMid(HttpServletRequest request,HttpServletResponse response){
//取得用户提交的mid
String mid = request.getParameter("mid");
if (mid == null || "".equals(mid)){
try {
response.getWriter().print("false");
} catch (IOException e) {
e.printStackTrace();
}
}else {
//得到业务层操作接口对象
IMemberService memberService = ServiceFactory.getMemberService();
//保存是否查找到该用户id的结果,如果查到为true,否者为false
boolean flag = memberService.checkMid(mid);
//在页面中打印出检查mid的结果,如果id已存在打印false,否者打印true
try {
response.getWriter().print(!flag);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 用户名的检测属于异步处理操作,所以在整个的处理流程之中也需要按照之前的验证码方式处理
- 修改JavaScript代码中的 validateMid()函数
//保存mid异步验证是否存在的结果
var midCheckBoolean = false;
function validateMid(){
var midEle = document.getElementById("mid");
//得到提元素
var hintEle = document.getElementById("midSpan");
var str = midEle.value;
if(str == ""){
hintEle.innerHTML = "<font color='red'>用户名不能为空</font>";
}else{
createXMLHttpRequest();
xmlHttpRequest.open("post","MemberServlet/checkMid?mid="+str);
xmlHttpRequest.send(null);
xmlHttpRequest.onreadystatechange = function(){
if(xmlHttpRequest.readyState == 4){
if(xmlHttpRequest.status == 200){
var text = xmlHttpRequest.responseText;
if(text == "true"){
midCheckBoolean = true;
hintEle.innerHTML = "<font color='green'>该用户名可以使用</font>";
}else{
midCheckBoolean = false;
hintEle.innerHTML = "<font color='red'>该用户名不可以使用</font>";
}
}else{
midCheckBoolean = false;
hintEle.innerHTML = "<font color='red'>该用户名不建议使用</font>";
}
}
}
}
return midCheckBoolean;
}
- 最终效果
- 以上完成了一个最基础的注册程序