1.项目介绍
商城网站:
功能:注册、登录、注销、记住用户名、30天自动自动登录、全站乱码过滤
2.EasyMall环境搭建
- 工作环境:window系统、服务器--tomcat、MyEclipse、Mysql
- 开发环境:jdk1.6
- 开发内容:
开发一个www.easymall.com网站,并且将其配置为一个缺省的虚拟主机。在其中添加EasyMall web应用,将应用配置为缺省web应用,且配置缺省主页。
- 创建虚拟主机---www.easymall.com网站:
在server.xml中添加一个Host标签,内容如下:
|
修改server.xml中
|
配置hosts文件 ,添加内容如下:
127.0.0.1 www.easymall.com |
创建EasyMall应用
Myeclipse中新建web应用:
发布web应用:
选择custom location 因为我们的www.easymall.com虚拟主机管理的文件是D:www.easymall.com,而不是在tomcat自己中,所以要选择自定义位置。
修改成ROOT是因为想缺省web应用
f. 准备页面
在课前资料中导入首页的三部分:头部、身体和尾部
i. 将以上三个HTML页面转换成jsp页面:
1. 创建与HTML页面对应名称的jsp页面。
2. 复制HTML中的全部内容
3. 粘贴在对应名称的jsp页面中,只保留jsp页面的第一行。(第一行修改字符集为utf-8)
4. 删除所有HTML页面
g. 将头部身体和尾部组合:
一个网站的头部和尾部可能需要频繁使用,如果在每一个网页中单独书写这些内容,十分不便,并且,将来维护修改的时候,工作量很大。所以讲它们抽取成两个单独的页面,在需要的页面中将它们引入。
i. 代码实现:有两种方式
1.是动态引入
<%@ page language="java" import="java.util.*" pageEncoding="utf-8" buffer="0kb"%> //jsp页面的文档说明,buffer="0kb"是设置缓存区大小,默认是8kb。设置0kb的意义是服务器响应的内容可以实时展现到客户端。
<%request.getRequestDispatcher("/_head.jsp").include(request, response); %> --%>
2.静态导入
<%@ include file="/_head.jsp" %>
首先是工具类
package com.easymall.utils; //web工具类 public class WebUtils { //构造方法私有化 private WebUtils() { } //非空校验 public static boolean isNull(String name) { return (name==null)||"".equals(name); } }
package com.easymall.utils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import com.mchange.v2.c3p0.ComboPooledDataSource; //Jdbc的工具类 public class JDBCUtils { //创建数据源,只完成一次初始化 private static ComboPooledDataSource source= new ComboPooledDataSource(); //获取数据源 public static ComboPooledDataSource getSource(){ return source; } //私有化构造方法 private JDBCUtils(){ } //获取连接 public static Connection getConnection() throws SQLException{ return source.getConnection(); } //关闭资源 public static void close(Connection conn,Statement stat,ResultSet rs) { if(rs !=null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); }finally{ rs = null; } } if(stat != null){ try { stat.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); }finally{ stat = null; } } if(conn != null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); }finally{ conn = null; } } } }
ComboPooledDataSou
c3p0.driverClass=com.mysql.jdbc.Driver
c3p0.jdbcUrl=jdbc:mysql://localhost:3306/easymall //easymall是创建的数据库
c3p0.user=root
c3p0.password=root
rce的配置文件
接下来是RegistServlet.servlet动态文件的书写,其作用是注册页面通过表单提交post的方式,请求到RegistServlet.servlet的dopost()方法转给doget()方法,在doget中实现 1.注册文本框非空状态的检测 2.密码的一致性 3.邮箱格式4JDBC用连接池技术访问数据库等
package com.easymall.servlet; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.easymall.utils.JDBCUtils; import com.easymall.utils.WebUtils; //注册Servlet public class RegistServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1.请求乱码处理 request.setCharacterEncoding("utf-8"); //这是设置服务器接收来自浏览器数据的编码格式 这是只适用于请求体中的内容 即post请求 get请求要先将数据转成二进制,然后根据需求的编码转换 //响应乱码处理 response.setContentType("text/html;charset=utf-8"); //这是设置浏览器端接受数据采用的编码格式 get和post请求都是可以用这个控制 //2.请求参数 String username = request.getParameter("username"); String password = request.getParameter("password"); String password2 = request.getParameter("password2"); String nickname = request.getParameter("nickname"); String email = request.getParameter("email"); String valistr = request.getParameter("valistr"); //验证码的 //3.非空校验 if(WebUtils.isNull(username)){ //将错误提示放入request域 request.setAttribute("msg", "用户名不能为空"); //使用请求转发,将request转发到regist.jsp页面 request.getRequestDispatcher("/regist.jsp").forward(request, response); //请求转发前后的代码会正常执行,应该在发生校验时打断代码。 return; } if(WebUtils.isNull(password)){ //将错误提示放入request域 request.setAttribute("msg", "密码不能为空"); //使用请求转发,将request转发到regist.jsp页面 request.getRequestDispatcher("/regist.jsp").forward(request, response); //请求转发前后的代码会正常执行,应该在发生校验时打断代码。 return; } if(WebUtils.isNull(password2)){ //将错误提示放入request域 request.setAttribute("msg", "确认密码不能为空"); //使用请求转发,将request转发到regist.jsp页面 request.getRequestDispatcher("/regist.jsp").forward(request, response); //请求转发前后的代码会正常执行,应该在发生校验时打断代码。 return; } if(WebUtils.isNull(nickname)){ //将错误提示放入request域 request.setAttribute("msg", "昵称不能为空"); //使用请求转发,将request转发到regist.jsp页面 request.getRequestDispatcher("/regist.jsp").forward(request, response); //请求转发前后的代码会正常执行,应该在发生校验时打断代码。 return; } if(WebUtils.isNull(email)){ //将错误提示放入request域 request.setAttribute("msg", "邮箱不能为空"); //使用请求转发,将request转发到regist.jsp页面 request.getRequestDispatcher("/regist.jsp").forward(request, response); //请求转发前后的代码会正常执行,应该在发生校验时打断代码。 return; } if(WebUtils.isNull(valistr)){ //将错误提示放入request域 request.setAttribute("msg", "验证码不能为空"); //使用请求转发,将request转发到regist.jsp页面 request.getRequestDispatcher("/regist.jsp").forward(request, response); //请求转发前后的代码会正常执行,应该在发生校验时打断代码。 return; } //4.密码一致性校验 if(password!= null&&password2!=null&&!password.equals(password2)){ request.setAttribute("msg", "两次密码不一致"); request.getRequestDispatcher("/regist.jsp").forward(request, response); return; } //5.邮箱格式校验 //[email protected] String reg = "\\w+@\\w+(\\.\\w+)+"; if(email!= null&&!email.matches(reg)){ request.setAttribute("msg", "邮箱格式不正确"); request.getRequestDispatcher("/regist.jsp").forward(request, response); return; } //6.验证码校验 //TODO:session //7.完成注册 //JDBC jar //用户名是否存在校验 Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JDBCUtils.getConnection(); ps = conn.prepareStatement("select * from user where username=?"); ps.setString(1, username); rs = ps.executeQuery(); if(rs.next()){ //用户名已存在,在页面中作出提示 request.setAttribute("msg", "用户名已存在"); request.getRequestDispatcher("/regist.jsp").forward(request, response); return; }else{ //用户名不存在,可以完成注册 conn = JDBCUtils.getConnection(); ps = conn.prepareStatement("insert into user values(null,?,?,?,?)"); ps.setString(1, username); ps.setString(2, password); ps.setString(3, nickname); ps.setString(4, email); ps.executeUpdate(); } } catch (SQLException e1) { e1.printStackTrace(); }finally{ JDBCUtils.close(conn, ps, rs); } //连接池 //8.跳转回首页 response.getWriter().write("恭喜注册成功,3秒后跳转回首页
"); response.setHeader("refresh", "3;url=http://www.easymall.com"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
f. 修改regist.jsp页面,添加代码如下
下方的三元表达式,是为了在用户第一次访问的时候不出现null,并且出现错误提示的时候能正常显示
<table> <tr> <td class="tds" colspan="2" style="color:red;text-align:center" ><%=request.getAttribute("msg")==null?"":request.getAttribute("msg") %>td> //获取request域对象中的msg这个域属性,如果有就是注册有不对的地方,在对应的span输出出错内容,如果为null就是没错的
这时候需要将span中的内容设置为空字符串
tr>
用户信息回显
用户某些信息已经确认,在提交之后,如果页面返回错误提示信息,应该讲部分内容回显,便于用户重新修改。就是方便用户体验
修改regist.jsp页面 <td>
<input type="text" name="username" value="<%=request.getParameter("username")==null?"":request.getParameter("username")%>"/> //从request请求中获得需要的上一次的请求参数,用于用户回显上一次数据,
如果第一次点击进来,那么参数值为null,那么回显的内容就是""
td> <tr> <td class="tds">昵称:td> <td> <input type="text" name="nickname" value="<%=request.getParameter("nickname")==null?"":request.getParameter("nickname")%>"/> td> tr> <tr> <td class="tds">邮箱:td> <td> <input type="text" name="email" value="<%=request.getParameter("email")==null?"":request.getParameter("email")%>"/> td> tr>
添加前台页面的校验
1. 添加前台页面校验的意义
如果只作出后台校验,可以防止无效数据入库,但是仍然不能降低服务器的访问压力,所以如果出现不合理的提交数据,应该在前台页面就完成拦截。如此才能降低服务器的访问压力。
可以通过js来完成页面拦截的效果。
2. 在页面中添加js校验
a. 修改form表单,添加onsubmit属性
<form action="<%=request.getContextPath() %>/RegistServlet" method="POST" onsubmit="return formObj.checkForm()"> onsubmit是form表单中的一个属性 =true是就是表单提交的时候回跳转,=false时就是表单
不会跳转到action中指定的页面。 checkForm()是一个方法。它可以校验表单的内容是否符合要求,符合返回true,反之为false
b. 在所有input框后添加span框。
<input type="text" name="username" value="<%=request.getParameter("username")==null?"":request.getParameter("username")%>"/> <span>span> //这个span的作用是跟在input后面用于存放错误信息的框
c.添加js代码
var formObj = { "checkForm":function(){ var canSub = true; //1.获取参数 //2.作出校验 //非空校验 /* var username = $("input[name='username']").val(); //清空操作 $("input[name='username']").nextAll("span").text(""); if(username == ""){ $("input[name='username']").nextAll("span").text("用户名不能为空").css("color","red"); } */ canSub = this.checkNull("username", "用户名不能为空") && canSub; canSub = this.checkNull("password", "密码不能为空") && canSub; canSub = this.checkNull("password2", "确认密码不能为空") && canSub; canSub = this.checkNull("nickname", "昵称不能为空") && canSub; canSub = this.checkNull("email", "邮箱不能为空") && canSub; canSub = this.checkNull("valistr", "验证码不能为空") && canSub; //密码一致性校验 canSub = this.checkPassword() && canSub; //邮箱格式校验 canSub = this.checkEmail() && canSub; return canSub; }, "checkNull":function(name,msg){ //非空校验 var tag = $("input[name='"+name+"']").val(); //清空操作 //$("input[name='"+name+"']").nextAll("span").text(""); this.setMsg(name, ""); if(tag == ""){ //$("input[name='"+name+"']").nextAll("span").text(msg).css("color","red"); this.setMsg(name, msg); return false; } return true; }, "checkPassword":function(){ //密码一致性校验 var password = $("input[name='password']").val(); var password2 = $("input[name='password2']").val(); if(password!="" && password2!="" && password!=password2){ this.setMsg("password2", "两次密码不一致"); return false; } return true; }, "checkEmail":function(){ //[email protected] var reg = /\w+@\w+(\.\w+)+/; var email = $("input[name='email']").val(); if(email!="" && !reg.test(email)){ this.setMsg("email", "邮箱格式不正确"); return false; } return true; }, "setMsg":function(name,msg){ //消息提示 span $("input[name='"+name+"']").nextAll("span").text(msg).css("color","red"); } };
d. 前台已经做出校验,后台为何仍需校验?
前台页面中的校验只是为了减少无效请求的做出的拦截效果,是使用js代码来实现的。并不能够真正防止无效数据信息加入数据库。而后台java代码当前做出的校验,才是在得到数据之后,真正的校验操作,在判断数据无效之后,可以防止无效数据入库。
e.添加离焦事件
//文档就绪事件 $(function(){ //为username输入框添加鼠标离焦事件 $("input[name='username']").blur(function(){ formObj.checkNull("username", "用户名不能为空"); }); $("input[name='password']").blur(function(){ formObj.checkNull("password", "密码不能为空"); }); $("input[name='password2']").blur(function(){ formObj.checkNull("password2", "确认密码不能为空"); formObj.checkPassword(); }); $("input[name='nickname']").blur(function(){ formObj.checkNull("nickname", "昵称不能为空"); }); $("input[name='email']").blur(function(){ formObj.checkNull("email", "邮箱不能为空"); formObj.checkEmail(); }); $("input[name='valistr']").blur(function(){ formObj.checkNull("valistr", "验证码不能为空"); });
添加ajax的用户名是否存在检测----实现局部刷新
编辑region.jsp文件
$("input[name='username']").blur(function(){ //离焦事件 formObj.checkNull("username", "用户名不为空"); //获取输入框中的值 var username=$("input[name=username]").val(); //ajax校验用户名是否存在 if(username!="") $("#username_img").load("<%=request.getContextPath() %>/AjaxCheckUsernameServlet",{"username":username}); });
创建AjaxCheckUsernameServlet, 处理请求
package com.easymall.servlet; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.easymall.utils.JDBCUtils; //ajax请求,查询数据库中用户名是否存在 public class AjaxCheckUsernameServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1.乱码处理 request.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); //2.获取请求参数 String username=request.getParameter("username"); //3.访问数据查询用户名是否存在 Connection conn=null; PreparedStatement ps=null; ResultSet rs=null; try { conn=JDBCUtils.getConnection(); ps=conn.prepareStatement("select * from user where username = ?"); ps.setString(1, username); rs=ps.executeQuery(); if(rs.next()){ //如果用户名存在,提示用户更该用户名 //做出响应 response.getWriter().write("用户名已存在"); }else { response.getWriter().write("用户名可以使用"); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { JDBCUtils.close(conn, ps, rs); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
修改EasyMall页面中的资源路径
1. 修改页面中的资源路径
a. 页面中的静态资源都是相对路径,在使用请求转发访问这些资源的时候,可能会导致路径加载错误。为了避免这个错误,可以在每一个静态资源路径之前拼接上web应用的虚拟路径。这样在静态资源加载的时候,就不会出现路径问题。
b. 不出现路径问题原因:
在静态资源之前拼接上web应用的虚拟路径,某些资源发生跳转时,替换的资源路径就会变为从web应用的路径级别开始替换。只要保证这一点,从web应用路径级别设置的静态资源路径就不会出错。
c. 修改方式:
各个页面中的静态资源之前,添加如下内容:
<%=request.getContextPath() %>/
添加验证码图片
1. 引入课前资料中的VerfiyCode
package com.easymall.utils; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.OutputStream; import java.util.Random; import javax.imageio.ImageIO; /** * 动态生成图片 */ public class VerifyCode { // {"宋体", "华文楷体", "黑体", "华文新魏", "华文隶书", "微软雅黑", "楷体_GB2312"} private static String[] fontNames = { "宋体", "华文楷体", "黑体", "微软雅黑", "楷体_GB2312" }; // 可选字符 //"23456789abcdefghjkmnopqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ"; private static String codes = "23456789abcdefghjkmnopqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ"; // 背景色 private Color bgColor = new Color(255, 255, 255); // 基数(一个文字所占的空间大小) private int base = 30; // 图像宽度 private int width = base * 4; // 图像高度 private int height = base; // 文字个数 private int len = 4; // 设置字体大小 private int fontSize = 22; // 验证码上的文本 private String text; private BufferedImage img = null; private Graphics2D g2 = null; /** * 生成验证码图片 */ public void drawImage(OutputStream outputStream) { // 1.创建图片缓冲区对象, 并设置宽高和图像类型 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); // 2.得到绘制环境 g2 = (Graphics2D) img.getGraphics(); // 3.开始画图 // 设置背景色 g2.setColor(bgColor); g2.fillRect(0, 0, width, height); StringBuffer sb = new StringBuffer();// 用来装载验证码上的文本 for (int i = 0; i < len; i++) { // 设置画笔颜色 -- 随机 // g2.setColor(new Color(255, 0, 0)); g2.setColor(new Color(getRandom(0, 150), getRandom(0, 150),getRandom(0, 150))); // 设置字体 g2.setFont(new Font(fontNames[getRandom(0, fontNames.length)], Font.BOLD, fontSize)); // 旋转文字(-45~+45) int theta = getRandom(-45, 45); g2.rotate(theta * Math.PI / 180, 7 + i * base, height - 8); // 写字 String code = codes.charAt(getRandom(0, codes.length())) + ""; g2.drawString(code, 7 + i * base, height - 8); sb.append(code); g2.rotate(-theta * Math.PI / 180, 7 + i * base, height - 8); } this.text = sb.toString(); // 画干扰线 for (int i = 0; i < len + 2; i++) { // 设置画笔颜色 -- 随机 // g2.setColor(new Color(255, 0, 0)); g2.setColor(new Color(getRandom(0, 150), getRandom(0, 150), getRandom(0, 150))); g2.drawLine(getRandom(0, 120), getRandom(0, 30), getRandom(0, 120), getRandom(0, 30)); } // 4.保存图片到指定的输出流 try { ImageIO.write(this.img, "JPEG", outputStream); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); }finally{ // 5.释放资源 g2.dispose(); } } /** * 获取验证码字符串 * @return */ public String getCode() { return this.text; } /* * 生成随机数的方法 */ private static int getRandom(int start, int end) { Random random = new Random(); return random.nextInt(end - start) + start; } public static void main(String[] args) throws Exception { VerifyCode vc = new VerifyCode(); vc.drawImage(new FileOutputStream("d:/vc.jpg")); //指定生成的图片放置的地方 System.out.println("执行成功~!"); } }
我们用这个验证码类 生成验证码图片放置在我么的网上
创建ValidateServlet
package com.easymall.servlet; import java.io.FileOutputStream; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.easymall.utils.VerifyCode; //验证码生成的Servlet public class ValidateServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //控制浏览器不使用缓存 response.setDateHeader("Expires", -1); response.setHeader("Cache-Control", "no-cache"); VerifyCode vc = new VerifyCode(); //当前servlet只有img标签调用,所以将图片放入缓冲区, //最终会在其调用的位置输出在浏览器中。 vc.drawImage(response.getOutputStream()); String code = vc.getCode(); System.out.println(code); System.out.println("执行成功~!"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
3. 修改regist.jsp页面
在文档就绪事件($(function(){}))中添加如下内容:
//等待页面加载完成后,为验证码图片绑定单击事件,更新验证码 ,实现的效果就是点击验证码图片更新验证码
$("#img").click(function(){ var date = new Date(); var time = date.getTime(); $(this).attr("src","<%=request.getContextPath()%>/ValidateServlet?time="+time); //给这个验证码图片的src属性改成生成验证码图片servlet文件的路径 });
a. 添加time参数的原因:
因为http协议的设计 浏览器认为每次发送的url地址如果相同,则不需要发送新的请求,为了能够通过单击事件加载新的验证码,需要在url后添加一个变化的参数,保证每一次访问的地址不相同,这样就会发送请求,ValidateServlet创建并返回最新的验证码。
修改EasyMall页面中的资源路径
1. 修改页面中的资源路径
a. 页面中的静态资源都是相对路径,在使用请求转发访问这些资源的时候,可能会导致路径加载错误。为了避免这个错误,可以在每一个静态资源路径之前拼接上web应用的虚拟路径。这样在静态资源加载的时候,就不会出现路径问题。
b. 不出现路径问题原因:
在静态资源之前拼接上web应用的虚拟路径,某些资源发生跳转时,替换的资源路径就会变为从web应用的路径级别开始替换。只要保证这一点,从web应用路径级别设置的静态资源路径就不会出错。
c. 修改方式:两种方法
1.删除掉servlet文件的虚拟路径前的/servlet.
2. 各个页面中的静态资源之前补充上web应用的虚拟路径名称就好,添加如下内容:
<%=request.getContextPath() %>/
<link rel="stylesheet" href="<%=request.getContextPath() %>/css/regist.css"/>