JavaWeb应用之实现网站注册和登录功能

JavaWeb中包含的技术有HTML、CSS、JS、jQuery、Request、Response、Servlet、JSP、Ajax、Cookie、Session、JDBC等,本文介绍下如何通过这些技术实现网站注册和登录功能,本文主要分为两个部分:第一个部分偏理论,主要是介绍一些技术和实现细节;第二个部分偏实践,主要是自己独立完成过程中遇到的bug和总结。
一、第一部分:
1、完成注册功能
首先是在_head.jsp里的注册标签里添加<%=request.getRequestContextPath%>/RigistServlet;
如果用户点击注册这个按钮,则浏览器会去后台请求RigistServlet,那RegistServlet里要写哪些东西?
1)请求和响应乱码处理
2)获取表单中的值:用request.getParameter();(这个是浏览器设置,Attribute是后台设置。)
3)校验
(1)非空校验:用户名、密码、确认密码、昵称、邮箱和验证码
(2)密码和确认密码一致性校验
(3)邮箱正确性校验
(4)验证码校验
(5)连接数据库校验用户名是否已存在,如果不存在则录入数据库
4)如果通过所有校验,则用定时刷新显示3秒后跳转回首页。(response的定时刷新和请求重定向有什么关联?前者包括后者,具体可以看第二个部分内容。)
要实现这个Servlet,还需要填写两个工具类:WebUtils和JDBCUtils,前者用于非空校验,后者用于JDBC连接。
注:
1)为什么所有校验里都要加上return?因为只要有一个校验不通过,那就不需要继续下面的步骤了,当然最后一个数据库校验有没有return都没关系,因为后面没有校验了。
2)因为前端有jQuery校验了,那后台的非空校验、密码和确认密码一致性校验和邮箱密码正确性校验是不是没有必要了?
2、c3p0
在学习MySQL的时候知道JDBC的六步:
注册数据库驱动;
获取数据库连接;
创建传输器;
使用传输器传输数据并返回结果;
遍历结果;
关闭资源。
如果只是一个地方用到JDBC,那创建对象、销毁对象都正常,但如果很多地方都用到JDBC,那每次都创建和销毁对象就会很浪费资源,所以考虑用连接池,比如c3p0,那什么是连接池?连接池是已经创建好连接对象的池子,所以每个请求不再重新创建对象,而是从池子里拿,关流也不需要,直接放回池子即可。
那怎么使用c3p0?
首先在lib里导入两个包:数据库的包和c3p0包,用于支持JDBCUtils类的编写。
然后在src内编写一个properties或者xml文件,用于配置c3p0.driverClass、c3p0.jdbcUrl、c3p0.user和c3p0.password。
最后就是编写JDBCUtils工具类,采用单例模式:
1)构造方法私有化;(这样该类就只能类.静态方法调用,不能实例化了。)
2)私有化静态对象ComboPooledDataSource;
3)创建两个静态方法:getConnection()和close()。
:有个bug想了很久,即为什么Eclipse里有c3p0包但电脑具体位置会没有?因为这是Eclipse的bug,不是个人的,解决方法就是需要手动复制粘贴这个包。
3、完成注册功能里的Ajax
为了提高用户体验感,即让用户在输入用户名并把鼠标离开输入框之后就可以在输入框后面显示用户是否可用的提示,那怎么实现这个需求?使用Ajax技术,这个技术全称是asynchronous js and xml,即异步js和xml,异步可以理解为浏览器向后台发出用户名校验的请求,但浏览器页面不变,而且不需要等待该请求的响应结果,随时可以操作其他指令,比如在注册表单中的密码输入框输入等。
该技术能实现的原理是XmlHttpRequest对象+JS,当然在jQuery中可以直接使用封装好的
$(“#username_msg”).load(url,data,callback)
或者直接
$.ajax(url,data,callback)
上面讲的更多的是Ajax是什么,那Ajax在注册功能中怎么使用?
1)前端JSP页面:
首先在regist.jsp中的鼠标离焦事件中添加一个函数,来判断输入框中是否为null,如果不是null则调用Ajax方法,如下所示:
$(“#username_msg”).load(url,data)
其中,url设置为<%=request.getContextPath%>/
AjaxCheckUsernameServlet,data设置为{“username”:username};当然也可以学习Get提交,把data拼接到urll后面,如下所示:
"<%=request.getContextPath%>/AjaxCheck
UsernameServlet?username=+"username
这个方法用到username,所以在此之前必须用request.getParameter(“username”)得到表单中的username值,不对!这是后台获取表单值的方法,如果是在前端的JSP页面中,则使用如下代码:$(this).val()
2)后端Servlet:
这里的AjaxCheckUsernameServelt和前面RegistServlet的前两步一样:先是请求和响应乱码处理,再是获取注册表单数据,这里获取方法才是前面提到的用getParameter(),和RegistServlet不同的是,这里只需要获取username的值,这里获取值之后只需要把该值连接数据库进行用户名是否存在的校验,其实就是把RegistServlet里的代码抽一部分出来重新整合,所以难的会写,其他也就熟悉了。当然,这里也用到了JDBCUtils,所以这个工具类必须要会写。
这里有个Bug困扰很久:
$(“$username_msg”).load(url,data)
这行代码看似没错,其实第二个$不对,应该是#,这个对应id选择器,如果是类选择器,则用.。
4、完成注册功能里的验证码校验
首先回顾下代码中用validate来表示验证码,为了生成验证码,所以需要在utils包里引入一个验证码工具类生成验证码,这里用到IO流,因为这部分代码不是很熟悉,因此先不自己写,而是直接拿来用。
在regist.jsp页面中两处地方用到验证码,第一处是生成验证码图片,直接设置img闭合标签中的属性src=”<%=request.getContextPath()%>/
ValidateServelt”,然后在ValidateServlet里写好如下内容:
因为这里没用到汉字,所以不需要对请求和响应进行乱码处理,这里也不需要从表单中获取数据,而是直接把验证码图片作为响应结果发送给表单中,所以利用验证码工具类创建一个对象,并调用该类里的drawImage方法,其中的参数是response.getOutputStream()。
上面这个只是为了在前端注册表单中显示验证码图片,那如何进行验证码校验呢?首先是调用验证码工具类中的getCode()获取验证码code,然后再用session域来传递code,在ValidateServlet里setAttribute(“code”,code),那在哪个Servlet里使用Session域的getAttribute(“code”)?当然是RegistServlet,需要注意的是使用getAttribute()方法后前面要用String进行强制转换;得到参数后需要和前端表单获取的valister值进行比较,因为都是String,所以这里不用equal,而是用更专业的equalsIgnoreCase,在验证码校验中专业体现不区分大小写;对了,校验当然是为了校验出不正确的,所以用!,和前面的校验一样,这里采用request的域对象功能,即request.setAttribute(“msg”,”验证码错误”)就会把“msg”,“验证码错误”这个参数传给前端表单,前端表单显示验证码错误信息的代码如下:

<tr>
<td class="tds" colspan="2" style="color:red;text-align:center">		           <%=request.getAttribute("msg")==null?"":request.getAttribute("msg") %>
</td>
</tr>

:如果是用JSP的el表达式,则直接${msg}即可,这里不需要指定requestScope,因为会自动从小到大搜索四个域里的msg。
这个参数传递完之后,还是用request的请求转发功能,把页面跳转回主页。
对了,为了在后台也能看到验证码,所以在ValidateServlet里最后一行打印出code。
但需要注意的是验证码图片是要实时更新的,所以不能有缓存,那就要代码的最前面加上response的控制缓存功能。
5、登录功能
会使用Ajax和验证码,则意味着注册功能基本完成,接下来就是登录了。
相对于注册而言,登录较为简单,可以理解为注册的简易版:
和注册一样先是请求和响应乱码处理,再是获取前端登录表单数据中的用户名和密码,然后把这两个参数连接数据库进行校验,即查询,如果存在则把数据放到session域然后再用response的重定向功能跳转到网址所在首页,那为什么放到session域?因为重定向跳转到首页后,此时首页和以前首页不一样,需要添加“欢迎某某,回来”,这个某某就是从session域中获取;
如果登录功能只有这些,当然会显得有点简单,所以考虑加上cookie和session技术,前者用于浏览器端,后者用于服务器端,cookie可以实现登录功能中的记住用户名和30天内自动登录,session其实已经用到了,就是上面的“欢迎某某,回来”,在注册功能中的例子是用于验证码校验中ValidateServlet和RegistServlet之间验证码code共享,还可以用于购物车Servlet和支付Servlet之间的商品参数prod共享。

二、第二部分:
1、独立完成注册功能的前台和后台:
A、前台
1)修改_head.jsp、_foot.jsp和index.jsp
(1)在_head.jsp的注册标签中分别添加RegistServlet路径;
(2)在index.jsp中body最开始和最后分别引入_head.jsp和_foot.jsp,有两种方法引入:**一种是使用request的请求包含功能;另一种是使用JSP三大指令中的include指令,比如<%@ include file=”/_head.jsp”%>。
2)编写regist.jsp
(1)Bug1
下面这个是通过el表达式添加了${pageConetxt.request.contextPath},但不对,不能添加!因为css、js和jsp这个页面同级别。

<!-- 引入CSS -->
<link rel="stylesheet"
	href="${pageConetxt.request.contextPath}/css/regist.css" />
<!-- 引入jQuery -->
<script type="text/javascript"
src="${pageContext.request.contextPath}/js/jQuery-1.4.2.js}"></script>

(2)Bug2
鼠标离焦事件为什么不能实现效果?
charset=UTF-8这个UTF大写还是小写应该没事。

<script type="text/javascript" src="js/jQuery-1.4.2.js}"></script>

上面代码中两个部分都有问题,一是把Q改成小写q,二是把}去掉,因为要和真实存在的文件名完全对应。
(3)疑惑
疑惑1:为什么加入如下加粗的部分:
“checkNull”:function(name,msg){
//非空校验
var tag = $(“input[name=’”+name+"’]").val();
//清空操作
this.setMsg(name, “”);
if(tag == “”){
this.setMsg(name, msg);
return false;
}
return true;
},
第一个是为了清空操作,什么是清空操作?我们绑定了鼠标离焦事件,如果没有在输入框输入数据,则会在后面的span显示某某不能为空,但如果此时填写了数据那后面的这个提示消息还在吗?还在,那怎么办?就需要这个清空操作。
后面两个return目前还不知道什么用?这个不影响目前的需求,所以自己写的代码就不加了。
疑惑2:为什么自己写的表单里的密码和确认密码输入的值不是隐藏的?
因为密码和确认密码的type类型应该是password而不是text。
疑惑3:下面的attr是什么意思?

	$("#img").click(function(){
				var date = new Date();
				var time = date.getTime();			
				$(this).attr("src","${pageContext.request.contextPath}/
				              ValidateServlet?time="+time);
});

用于设置属性,即图片的来源,因为验证码图片绑定了鼠标单击事件,即这个验证码图片可以动态变化,所以指向一个Servlet。
B、后台
1)独立编写WebUtils类
这个类写的不熟练,要多看多写。
注:String的trim方法是去掉前后的空格

package com.huan.utils;

public class WebUtils {
    private WebUtils(){
    	
    }
    public static boolean isNull(String name){
    	return "".equals(name.trim())||name == null;
    }
}

2)独立编写JDBCUtils类
这个涉及单例模式,非常重要!
1)私有化无参构造(外界不能new实例)
2)私有化静态线程池对象(只创建一个线程池对象)
3)静态方法getConnection()(只需调用线程池对象的getConnection())
+
静态方法close()(if+try-catch-finally)
注:JDBCUtils中c3p0线程池的对象名称ComboPooledDataSoure。
3)独立完成RegistServlet
RegistServlet里的主要内容在第一部分里面已经介绍了,这里只是讲下自己遇到的困难:
(1)==和equals选哪个?
==比较的内容分两种情况讨论:对于String等引用类型比较的是地址;而对于基本数据类型比较的是值。
而equals方法只能用于引用类型的比较,而且比较的是地址,如果想比较值,那就需要对equals方法进行重写,比如常用的String类里的equals方法是已经对顶级父类Object的equals方法进行重写,所以比较的是值,比如下面代码应该把!=改成!password.equals(password2)。

if(password!=""&&password2!=""&&password!=password2){
			request.setAttribute("msg", "两次密码不一致");
		request.getRequestDispatcher("/regist.jsp").forward(request, response);
			return;
		}

(2)邮箱格式验证
这里的正则和regist.jsp里的不一样,不是很熟,还是需要记住。

String reg = "^\\w+@\\w+(\\.\\w+)+$";
		   if(!email.matches(reg)){
			   request.setAttribute("msg", "邮箱格式错误");
			   request.getRequestDispatcher("/regist.jsp").forward
			   (request, response);
			   return;
		   }

这里附上regist.jsp里的邮箱格式校验:

	"checkEmail":function() {
			var reg = /\w+@\w+(\.\w+)+/;
			var email = $("input[name='email']").val();
            if(email!=""&&!reg.test(email)){
              this.setMsg("email","邮箱格式不正确");
            }
		},

(3)验证码校验

        String code = (String) request.getSession().getAttribute("code");
	    if(!code.equals(valistr)){
	    	request.setAttribute("msg", "校验码不正确");
	    	request.getRequestDispatcher("/regist.jsp").forward(request, response);
	    	//最后一个return可加可不加
	    	return;
	    }

最好把equals改成equalsIgnoreCase,因为后者不区分大小写更符合实际。
(4)Bug:
request.setCharacterEncoding(“utf-8”);
response.setCharacterEncoding(“charset:utf-8”);
需要把冒号改成等于号,并在前面最好加上”text/html;”(那后面响应的输出也必须是html标签形式?不一定)。
最主要的是response的api调错了,应该是setContentType。
4)独立完成AjaxCheckServlet
(1)AjaxCheckServlet要不要请求和响应乱码处理?因为涉及中文,所以肯定要的。
(2)jsp中load函数里有了参数传递过来,那怎么获取这个值,还是用request的getParameter()?是的,而且因为有load函数,所以把想要输出的数据直接写到response里,response自动会放到load前面绑定的选择器所在的标签。
(3)数据库查询语句如下所示:
rs = ps.execute();
上面这个有错,不要用这个笼统的api(因为这个只是返回true或者false,不是具体行等数据),如果是查询数据那就executeQuery(),如果是插入数据那就executeUpdate()。
5)独立完成ValidateServlet(VeriyCode这个工具类直接放入)
ValidateServlet的核心代码如下:

            VerifyCode code = new VerifyCode();
            code.drawImage(response.getOutputStream());
            //怎么实现点击验证码图片就会刷新
            
            //把验证码具体数字打印到Eclipse控制台
            System.out.println(code.getCode());
            
            //把验证码具体数字放到session域,让其和RegistServlet共享
            request.getSession().setAttribute("code", code);

主要解决以下问题:
(1)如何实现点击验证码图片就会刷新?
那就要想到为什么点击不刷新的原因?缓存!
那怎么设置缓存?用request还是response?显然是通知浏览器不要用缓存,那就是response,具体代码如下所示:
response.setDateHeader(“Expires”, -1);
response.setHeader(“Cache-control”, “no-cache”);
还是不行?所以问题不在缓存,在于regist.jsp中的事件:

     $("input=[name='valistr']").blur(function(){
	    formObj.checkNull("valistr", "验证码不能为空");
	    var date = new Date();
	    var time = date.getTime();
        $(this).attr("src","${pageContext.request.contextPath}/
        ValidateServlet?time="+time);
	  });
	});

blur是鼠标离焦事件,一般用于输入框的鼠标离焦,而这里是验证码图片而不是输入框,所以应该改成鼠标单击事件click。
但上面这个代码也是有问题的,下面的date等代码不是绑定
(“input=[name=’valistr’]”)这个选择器,而是$(“#img”),并且为click事件。
(2)注意代码命名规范,黄色部分改成vc,再把vc.getCode命名为String的code。
2、独立完成登录功能的前台和后台
A、前台
1)修改_head.jsp、_foot.jsp和index.jsp
(1)在_head.jsp的登录标签中分别添加LoginServlet路径;
(2)在index.jsp中body最开始和最后分别引入_head.jsp和_foot.jsp,有两种方法引入:一种是request的请求包含;另一种是JSP三大指令中的include指令,比如<%@ include file=”/_head.jsp”%>。
2)独立编写login.jsp
(1)CSS引入有提示,用link标签,那引入jQuery用什么标签表示?script标签里src属性。
(2)form标签里修改提交方式为POST用什么属性?method
(3)一直在说form表单,那忘了里面也要用table标签规范格式,而且tr表示行也是在table标签里的(没有则会警告)
(4)记住table里的td标签的一些属性:
class=”tds”、colspan=”2”(跨两列)、style=”text-align:center”等。
(5)为了提示登录的用户名是否存在,所以在用户名输入框的上面弄一个消息提示:

 <tr class="tdx" colspan="2" style="color:red;text-align:center">${msg}</tr>

上面这个不对,应该把tr改成td,并且把tr提到外面,tr里面不加任何属性。

(6)登录界面和注册界面不同,不需要前端校验和绑定一些鼠标离焦事件或者验证码单击事件,所以不需要JS和jQuery,而是直接用JSP的脚本表达式来完成,用到if判断username是否null从而觉得首页第一个div里的内容是登录+注册还是欢迎某某回来+注销,当然用el表达式加jstl标签库也可以完成。
B、后台
1)独立编写LoginServlet
(1)如果登录成功要转发到www.huanletao.com的首页,那如下代码有错吗
response.sendRedirect(“www.huanletao.com”);
有错,因为网址要写全,不然默认是在同一个web应用下的www.huanletao.com,所以如果是网址要写成http://www.huanletao.com。
(2)如何实现记住用户名?
这个用到cookie,但对于cookie的使用并不熟悉,所以先弄个cookieDemo的Servlet:
步骤1:请求和响应乱码处理
步骤2:产生一个时间值

这里用到Date对象的toLocaleString(),用于返回该日期对象的字符串,但其实这个方法已经过时;
步骤3:实现cookie
怎么给浏览器传输cookie?肯定是用response这个对象,然后调用它的setHeader方法,参数分别是”set-cookie”和time。
那下次请求的时候,怎么在服务器获取这个cookie?方法是用getHeader,从而与其他的setHeader对应,但此时是用response还是request?那肯定是request的,因为是从浏览器中获取。
从浏览器中得到cookie后怎么办?判断cookie是否为null,然后调用response的输出函数输出不同的响应数据。
第一次访问浏览器的结果里跟着“JSESSIONID=某个值”,这个值没有自己设置,为什么自动出现?是的,会话一旦建立,服务器会给这个浏览器开辟一个这次会话的内存来存放该会话的一些内容,并用一个jsessionid来和这个内存对应,理论上Servlet代码里没要求显示jsessionid,出现的原因是Google浏览器的问题,改成IE浏览器就很正常。
上面用到response.setHeader(“set-cookie”,time)和request.getHeader(“cookie”)可以实现cookie效果,但在有些浏览器效果不好,所以为了在所有浏览器通用,采用Cookie类来实现,Cookie包含四个部分:名字、路径、时长和domain,因为所有Cookie的domain默认一样,所以这个不管,一般来说除了时长外其他三个都一样那就是同一个Cookie,所以删除Cookie就是先令这三部分一样,然后再令时长为0即可。如果Cookie的属性都设置好了,那就调用response的addCookie(cookie)把cookie发送到响应对象中。那怎么接收Cookie?request.getCookies(),但注意这个返回的是数组,所以获取Cookie数组后用增强for循环遍历。但需要注意的是如果不像上面一样是时间这样的数值而是名称,那就要进行乱码处理,如下所示:

Cookie cookie = new Cookie("remname",
                URLEncoder.encode(username, "utf-8"));

而在JSP的table中遍历获取Cookie数组之后不能直接username=remnameC.getValue();而是:

String username = URLDecoder.decode(remnameC.
                   getValue(), "utf-8");

上面这个有问题,因为后面的name标签要设置value:
value=”<%=username ==””?””:”checked=’checked’”%>”
所以username要设置为全局变量,故把String放到外面,这里不涉及线程安全,所以设置为全局变量没事。
2)独立编写LogOutServlet
这个代码比较简单,就是把session去掉即可,所以先getsession()再调用session的invalidate()这个方法即可,只是需要注意的是getsession()里的参数要写成false,这样如果没有浏览器的session也不会创建新的session,而是返回null,既然都要销毁session那就没必要浪费内存再生产新的,所以false很有必要加上。
去掉session后,就要利用response的请求重定向到网站首页。

发布了6 篇原创文章 · 获赞 1 · 访问量 83

猜你喜欢

转载自blog.csdn.net/Zb20171027/article/details/104226558