第五阶段 分页模型
分页实现
根据上图所述流程,首先需要创建一个Page对象,保存以下几个页面属性:
- pageNo 当前页码
- pageTotal 总页码
- pageTotalCount 总记录数
- pageSize 每页显示数据
- items 当前页面数据
public class Page<T> {
private static final Integer PAGE_SIZE = 4;
private Integer pageNo; // 当前页码
private Integer pageTotal; // 总页码
private Integer pageTotalCount; // 总记录数
private Integer pageSize = PAGE_SIZE; // 当前页显示数量
private List<T> items;
public Integer getPageNo() {
return pageNo;
}
public void setPageNo(Integer pageNo) {
this.pageNo = pageNo;
}
public Integer getPageTotal() {
return pageTotal;
}
public void setPageTotal(Integer pageTotal) {
this.pageTotal = pageTotal;
}
public Integer getPageTotalCount() {
return pageTotalCount;
}
public void setPageTotalCount(Integer pageTotalCount) {
this.pageTotalCount = pageTotalCount;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public List<T> getItems() {
return items;
}
public void setItems(List<T> items) {
this.items = items;
}
@Override
public String toString() {
return "Page{" +
"pageNo=" + pageNo +
", pageTotal=" + pageTotal +
", pageTotalCount=" + pageTotalCount +
", pageSize=" + pageSize +
", items=" + items +
'}';
}
}
与之前的JavaBean对象不同,页面Page对象在之前JavaBean对象的基础上增加了泛型,这是因为,分页操作可以在很多方面有具体使用,例如用户分页,商品分页,图书分页,信息分页等,那么传入进来的对象并不是固定的,所以使用泛型来接收对象!
接下来,需要编写BookServlet程序中的page方法,但是需要注意,当用户点击图书管理时,执行的方法不再是list方法,而是page方法,所以需要在manager_manu中修改action的参数值:
<a href="manager/bookServlet?action=page">图书管理</a>
编写BookServlet程序:
// 处理分页功能
protected void page(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获得请求的参数pageNo和pageSize
int pageNo = WebUtils.parseInt(req.getParameter("pageNo"), 1);
int pageSize = WebUtils.parseInt(req.getParameter("pageSize"), Page.PAGE_SIZE);
// 调用bookService.page(pageNo, pageSize)
Page<Book> page = bookService.page(pageNo, pageSize);
// 保存Page对象到request域中
req.setAttribute("page", page);
// 请求转发至book_manager.jsp页面
req.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(req, resp);
}
按照上述思路,继续编写一个bookService.page方法:
@Override
public Page<Book> page(int pageNo, int pageSize) {
Page<Book> page = new Page();
page.setPageSize(pageSize); // 设置页面大小
// 求记录总数
Integer pageTotalCount = bookDao.queryForPageTotalCount();
page.setPageTotalCount(pageTotalCount); // 设置记录总数
// 求总页面
Integer pageTotal = pageTotalCount / pageSize;
if (pageTotalCount % pageSize > 0) {
pageTotal++;
}
page.setPageTotal(pageTotal); // 设置页码总数
page.setPageNo(pageNo); // 设置当前页面
// 求当前页面数据的开始索引begin
int begin = ( page.getPageNo() - 1 ) * pageSize;
// 求当前页数据
List<Book> items = bookDao.queryForPageItems(begin, pageSize);
page.setItems(items); // 设置当前页数据
return page;
}
**注意:**这里需要将page.setPageNo方法放在page.setPageTotal的后面,因为在之后的分页操作中,需要进行页数校验的操作,该操作需要使用到pageTotal参数对pageNo进行修正!
编写bookDao.queryForPageTotalCount和bookDao.queryForPageItems(begin, pageSize)方法:
@Override
public Integer queryForPageTotalCount() {
String sql = "SELECT COUNT(*) FROM `t_book`";
Number count = (Number) queryForSingleValue(sql);
// 需要注意的是,这里的返回值不能为Integer类型,否则会报错
return count.intValue();
}
@Override
public List<Book> queryForPageItems(int begin, int pageSize) {
String sql = "SELECT * FROM `t_book` LIMIT ?,?";
return queryForList(Book.class, sql, begin, pageSize);
}
按照以上逻辑编写完毕之后,进入book_manager.jsp页面,在使用JSTL标签库的遍历中,遍历的不再是Book对象,而是Page对象中的items!
<c:forEach items="${requestScope.page.items}" var="book">
<tr>
<td>${book.name}</td>
<td>${book.price}</td>
<td>${book.author}</td>
<td>${book.sales}</td>
<td>${book.stock}</td>
<td><a href="manager/bookServlet?action=getBook&id=${book.id}&method=update">修改</a></td>
<td><a class="deleteClass" href="manager/bookServlet?action=delete&id=${book.id}">删除</a></td>
</tr>
</c:forEach>
上一页、下一页、首页、指定页数跳转的实现
<div id="page_nav">
<c:if test="${requestScope.page.pageNo > 1}">
<a href="manager/bookServlet?action=page&pageNo=1">首页</a>
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo - 1}">上一页</a>
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo - 1}">${requestScope.page.pageNo - 1}</a>
</c:if>
【${requestScope.page.pageNo}】
<c:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}">
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo + 1}">${requestScope.page.pageNo + 1}</a>
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo + 1}">下一页</a>
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageTotal}">末页</a>
</c:if>
共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalCount}条记录
到第<input value="${param.pageNo}" name="pn" id="pn_input"/>页
<input id="searchPageBtn" type="button" value="确定">
</div>
上一页可以通过提交链接到BookServlet中的page方法,参数为request域中存放的page对象中当前页数page.no-1即可!下一页也同理!
但是需要注意,当页数为首页或者末页时,应该将上一页和下一页的跳转取消或者隐藏!以上代码实例使用了JSTL标签库中的判断,当前页数大于1时(pageNo>1)上一页和首页的跳转链接才会输出;当前页数小于页面总数时(pageNo > pageTotal)下一页和末页的跳转链接才会输出!
页面的指定跳转通过JQuery中提供的location.href方法实现,该方法可以获取当前页面完整的链接,这个方法可读可写,只要获取当前页码的参数值,然后写入该方法中即可!
// 页码指定跳转
$("#searchPageBtn").click(function () {
let pageNo = $("#pn_input").val();
location.href = "http://localhost:8080/book/manager/bookServlet?action=page&pageNo=" + pageNo;
})
但是,该链接不应该是固定的,而是动态的,联想到在jsp页面动态化时将重复代码段提取出来的操作,找到head.jsp,在该页面中对链接进行了动态获取的操作,于是可以将该链接存放在pageContext中:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String basePath = request.getScheme()
+ "://"
+ request.getServerName()
+ ":"
+ request.getServerPort()
+ request.getContextPath()
+ "/";
pageContext.setAttribute("basePath", basePath);
%>
然后再从location.href方法中获取basePath即可:
location.href = "${pageScope.basePath}manager/bookServlet?action=page&pageNo=" + pageNo;
完成以上步骤之后,程序仍然存在一些小问题,例如:用户可能在跳转页面中输入小于最小页码1或者大于页码总数的值,进行跳转之后,页面并不会显示图书信息!
于是,首先可以在前端页面中做一个if判断,以下采取的处理方案为:如果用户输入的页码小于1,则跳转页面至首页,如果用户输入的页码大于总页码数,则跳转页面至最后一页!
// 页码指定跳转
$("#searchPageBtn").click(function () {
let pageNo = $("#pn_input").val();
let pageTotal = ${requestScope.page.pageTotal};
if (pageNo < 1 ) {
pageNo = 1;
} else if(pageNo > pageTotal) {
pageNo = pageTotal;
}
location.href = "${pageScope.basePath}manager/bookServlet?action=page&pageNo=" + pageNo;
})
但这仅仅是在前端进行判断,有经验的用户有可能会在链接栏中输入页码,所以需要在服务器端进行页码的校验与矫正!
如果在BookService中进行校验操作,那就代表着在每个页面的分页操作中都需要进行校验操作,所以需要在Page类中的setPageNo方法进行校验:
public void setPageNo(Integer pageNo) {
if (pageNo < 1) {
pageNo = 1;
} else if (pageNo > pageTotal) {
pageNo = pageTotal;
}
this.pageNo = pageNo;
}
删除、添加、修改的页面修改
完成分页功能之后,再次点击删除、添加等操作的时候,会发现页面不显示任何图书信息,因为在之前的业务方法中,会跳转至页面页面list方法,所以需要将之前设置为跳转至list方法的业务方法都更换成跳转至page方法!
但是,完成以上操作之后,会发现用户执行删除、添加等功能之后,页面自动跳转的页数是第一页,为了优化用户体验,可以设置为:用户执行删除、添加等功能之后,自动跳转至修改过后的图书页面!
添加图书,跳转至最后一页(book_manager.jsp):
<td><a href="pages/manager/book_edit.jsp?method=add&pageNo=${requestScope.page.pageTotal}">添加图书</a></td>
隐藏域(book_edit.jsp)
<input type="hidden" name="pageNo" value="${param.pageNo}">
添加图书:
protected void add(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int pageNo = WebUtils.parseInt(req.getParameter("pageNo"), 0);
pageNo += 1;
// 获取请求的参数并封装成Book对象
Book book = WebUtils.copyParamToBean(req.getParameterMap(), new Book());
// bookService调动addBook()方法添加图书
bookService.addBook(book);
// 页面重定向
resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=page&pageNo=" + pageNo);
}
注意:添加图书会产生一个小问题,当添加的图书正好使得页面增加一页,也就是添加的图书正好是新页面的第一条数据时,在添加图书的方法中,需要对页面进行“始终+1”的操作!
删除图书,跳转至删除图书页:
<td><a class="deleteClass" href="manager/bookServlet?action=delete&id=${book.id}&pageNo=${requestScope.page.pageNo}">删除</a></td>
删除图书:
protected void delete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取id并调用工具类进行转型
int id = WebUtils.parseInt(req.getParameter("id"), 0);
// bookService调动deleteBook()方法添加图书
bookService.deleteBookById(id);
// 页面重定向
resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=page&pageNo=" + req.getParameter("pageNo"));
}
修改图书,跳转至修改图书页:
<td><a href="manager/bookServlet?action=getBook&id=${book.id}&method=update&pageNo=${requestScope.page.pageNo}">修改</a></td>
修改图书:
protected void update(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求的参数并封装成book对象
Book book = WebUtils.copyParamToBean(req.getParameterMap(), new Book());
// 调用bookService.updateBook方法修改图书
bookService.updateBook(book);
// 页面重定向
resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=page&pageNo=" + req.getParameter("pageNo"));
}
首页index.jsp的跳转
当用户访问首页的时候,首页需要展示图书,而此时并没有图书对象,也就是说,需要创建一个ClientBookServlet让首页index.jsp访问该程序并进入程序中的page分页方法展示图书!
但是有一个问题,纵观诸多网站的首页,从来没有首页的访问地址会是http://localhost:8080/book/manager/ClientBookServlet?action=page这样一串网址!
于是,可以将web目录下的index.jsp网页设置为负责请求转发到ClientBookServlet程序,然后该程序下的分页方法page在重定向至/pages/client目录下的index.jsp页面!
web目录下的index.jsp页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<jsp:forward page="client/bookServlet?action=page"></jsp:forward>
ClientBookServlet程序:
/**
* 处理分页功能
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void page(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获得请求的参数pageNo和pageSize
int pageNo = WebUtils.parseInt(req.getParameter("pageNo"), 1);
int pageSize = WebUtils.parseInt(req.getParameter("pageSize"), Page.PAGE_SIZE);
// 调用bookService.page(pageNo, pageSize)
Page<Book> page = bookService.page(pageNo, pageSize);
// 保存Page对象到request域中
req.setAttribute("page", page);
// 请求转发至book_manager.jsp页面
req.getRequestDispatcher("/pages/client/index.jsp").forward(req, resp);
}
}
需要注意的是,请求转发的路径应该为/pages/client/index.jsp路径!
client目录下的index.jsp页面:
<e:forEach items="${requestScope.page.items}" var="book">
<div class="b_list">
<div class="img_div">
<img class="book_img" alt="" src="${book.imgPath}" />
</div>
<div class="book_info">
<div class="book_name">
<span class="sp1">书名:</span>
<span class="sp2">${book.name}</span>
</div>
<div class="book_author">
<span class="sp1">作者:</span>
<span class="sp2">${book.author}</span>
</div>
<div class="book_price">
<span class="sp1">价格:</span>
<span class="sp2">¥${book.price}</span>
</div>
<div class="book_sales">
<span class="sp1">销量:</span>
<span class="sp2">${book.sales}</span>
</div>
<div class="book_amount">
<span class="sp1">库存:</span>
<span class="sp2">${book.stock}</span>
</div>
<div class="book_add">
<button>加入购物车</button>
</div>
</div>
</div>
</e:forEach>
首页的分页条
首页的分页条设置非常简单,只需要将之前写好的分页复制粘贴至首页,并修改路径即可!
<%-- 分页条的开始--%>
<div id="page_nav">
<c:if test="${requestScope.page.pageNo > 1}">
<a href="client/bookServlet?action=page&pageNo=1">首页</a>
<a href="client/bookServlet?action=page&pageNo=${requestScope.page.pageNo - 1}">上一页</a>
<a href="client/bookServlet?action=page&pageNo=${requestScope.page.pageNo - 1}">${requestScope.page.pageNo - 1}</a>
</c:if>
【${requestScope.page.pageNo}】
<c:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}">
<a href="client/bookServlet?action=page&pageNo=${requestScope.page.pageNo + 1}">${requestScope.page.pageNo + 1}</a>
<a href="client/bookServlet?action=page&pageNo=${requestScope.page.pageNo + 1}">下一页</a>
<a href="client/bookServlet?action=page&pageNo=${requestScope.page.pageTotal}">末页</a>
</c:if>
共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalCount}条记录
到第<input value="${param.pageNo}" name="pn" id="pn_input"/>页
<input id="searchPageBtn" type="button" value="确定">
</div>
<%-- 分页条的结束--%>
但通过观察可以发现,前台的分页条设置和后台的分页条设置,仅仅只是路径不同,所以可以将分页条代码抽取出来!但在抽取之前,需要在Page类中设置一个变量来保存地址,并设置其get/set方法!
Page类:
// 请求地址
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
分别在前台和后台的page方法中设置请求地址:
ClientBookServlet程序page:
page.setUrl("client/bookServlet?action=page");
BookServlet程序page:
page.setUrl("manager/bookServlet?action=page");
设置分页条:
<div id="page_nav">
<c:if test="${requestScope.page.pageNo > 1}">
<a href="${requestScope.page.url}&pageNo=1">首页</a>
<a href="${requestScope.page.url}&pageNo=${requestScope.page.pageNo - 1}">上一页</a>
<a href="${requestScope.page.url}&pageNo=${requestScope.page.pageNo - 1}">${requestScope.page.pageNo - 1}</a>
</c:if>
【${requestScope.page.pageNo}】
<c:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}">
<a href="${requestScope.page.url}&pageNo=${requestScope.page.pageNo + 1}">${requestScope.page.pageNo + 1}</a>
<a href="${requestScope.page.url}&pageNo=${requestScope.page.pageNo + 1}">下一页</a>
<a href="${requestScope.page.url}&pageNo=${requestScope.page.pageTotal}">末页</a>
</c:if>
共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalCount}条记录
到第<input value="${param.pageNo}" name="pn" id="pn_input"/>页
<input id="searchPageBtn" type="button" value="确定">
</div>
完成以上步骤之后,发现无论是前端和后端,分页条代码行已经是完全一致,可以对分页条进行抽取成page.jsp,然后用静态包含的方法调用分页条!
<%--静态包含分页条--%>
<%@include file="/pages/common/page.jsp"%>
区间搜索
ClientBookServlet程序:
protected void pageByPrice(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获得请求的参数pageNo和pageSize
int pageNo = WebUtils.parseInt(req.getParameter("pageNo"), 1);
int pageSize = WebUtils.parseInt(req.getParameter("pageSize"), Page.PAGE_SIZE);
// 获取请求的参数min和max
int min = WebUtils.parseInt(req.getParameter("min"), 0);
int max = WebUtils.parseInt(req.getParameter("max"), Integer.MAX_VALUE);
// 调用bookService.pageByPrice(pageNo, pageSize)
Page<Book> page = bookService.pageByPrice(pageNo, pageSize, min, max);
page.setUrl("client/bookServlet?action=pageByPrice&min=" + min + "&max=" + max);
// 保存Page对象到request域中
req.setAttribute("page", page);
// 请求转发至book_manager.jsp页面
req.getRequestDispatcher("/pages/client/index.jsp").forward(req, resp);
}
需要注意的是,在页码设置路径page.setUrl()时,需要传入参数min和max!
BookServlet程序:
@Override
public Page<Book> pageByPrice(int pageNo, int pageSize, int min, int max) {
Page<Book> page = new Page();
page.setPageSize(pageSize); // 设置页面大小
// 求记录总数
Integer pageTotalCount = bookDao.queryForPageTotalCountByPrice(min, max);
page.setPageTotalCount(pageTotalCount); // 设置记录总数
// 求总页面
Integer pageTotal = pageTotalCount / pageSize;
if (pageTotalCount % pageSize > 0) {
pageTotal++;
}
page.setPageTotal(pageTotal); // 设置页码总数
page.setPageNo(pageNo); // 设置当前页面
// 求当前页面数据的开始索引begin
int begin = ( page.getPageNo() - 1 ) * pageSize;
// 求当前页数据
List<Book> items = bookDao.queryForPageItemsByPrice(begin, pageSize, min, max);
page.setItems(items); // 设置当前页数据
return page;
}
与之前的设置页面page方法大概相同,只是多了参数min和max而已!
BookDao程序:
@Override
public Integer queryForPageTotalCountByPrice(int min, int max) {
String sql = "SELECT COUNT(*) FROM `t_book` WHERE `price` BETWEEN ? AND ?";
Number number = (Number) queryForSingleValue(sql, min, max);
return number.intValue();
}
@Override
public List<Book> queryForPageItemsByPrice(int begin, int pageSize, int min, int max) {
String sql = "SELECT * FROM `t_book` WHERE `price` BETWEEN ? AND ? ORDER BY `price` ASC LIMIT ?,?";
return queryForList(Book.class, sql, min, max, begin, pageSize);
}
**注意:**在市面上的任何网页,通过价格区间进行搜索之后,得到的图书都会按照从小到大的价格进行排序,所以在sql语句中需要加伤order by子句来进行排序!order by子句默认为升序(ASC),降序为DESC!
设置区间价格搜索回显
<form action="client/bookServlet" method="get">
<input type="hidden" name="action" value="pageByPrice">
价格:<input id="min" type="text" name="min" value="${param.min}"> 元 -
<input id="max" type="text" name="max" value="${param.max}"> 元
<input type="submit" value="查询" />
</form>
因为链接中已经带有了参数min和max,所以直接通过param便可获取!
解决用户只设置了min或者max其中一个值就进行查询操作的bug:
当用户不输入max值,只设置min值为0,那么在回显操作中,就会将ClientBookServlet程序中给max赋予的默认值进行回显,也就是Integer.MAX_VALUE(约为21亿),这很明显不符合页面规范,所以需要设置请求地址:
protected void pageByPrice(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获得请求的参数pageNo和pageSize
int pageNo = WebUtils.parseInt(req.getParameter("pageNo"), 1);
int pageSize = WebUtils.parseInt(req.getParameter("pageSize"), Page.PAGE_SIZE);
// 获取请求的参数min和max
int min = WebUtils.parseInt(req.getParameter("min"), 0);
int max = WebUtils.parseInt(req.getParameter("max"), Integer.MAX_VALUE);
// 调用bookService.pageByPrice(pageNo, pageSize)
Page<Book> page = bookService.pageByPrice(pageNo, pageSize, min, max);
StringBuilder sb = new StringBuilder("client/bookServlet?action=pageByPrice");
if(req.getParameter("min") != null) {
sb.append("&min=" + req.getParameter("min"));
}
if(req.getParameter("max") != null) {
sb.append("&max=" + req.getParameter("max"));
}
page.setUrl(sb.toString());
// 保存Page对象到request域中
req.setAttribute("page", page);
// 请求转发至book_manager.jsp页面
req.getRequestDispatcher("/pages/client/index.jsp").forward(req, resp);
}
使用StringBuilder类进行字符串的拼接,当request域中存在max值时,才对max值进行回显!如果不存在,在页面中显示为空,但是在服务器后台会取默认值进行区间搜索!
第六阶段
显示登录信息
当用户登录时,在菜单栏中需要显示用户的信息;当用户注销时,菜单栏需要显示登录或者注册的选项!在单个页面中,可以将用户信息保存在request域中,然后页面进行调用,但是当页面进行跳转时,也许要将用户信息再一次进行保存,非常繁琐!
为了实现该需求,可以将用户信息保存在Session域中!
UserServlet 程序中保存用户登录的信息:
} else {
// 保存用户信息至Session域中
req.getSession().setAttribute("user", loginUser);
req.getRequestDispatcher("/pages/user/login_success.jsp").forward(req, resp);
}
修改用户菜单栏:
<div>
<span>欢迎<span class="um_span">${sessionScope.user.username}</span>光临尚硅谷书城</span>
<a href="pages/order/order.jsp">我的订单</a>
<a href="index.jsp">注销</a>
<a href="index.jsp">返回</a>
</div>
修改主页菜单栏:
<div>
<e:if test="${empty sessionScope.user}">
<a href="pages/user/login.jsp">登录</a> |
<a href="pages/user/regist.jsp">注册</a>
</e:if>
<e:if test="${not empty sessionScope.user}">
<span>欢迎<span class="um_span">${sessionScope.user.username}</span>光临尚硅谷书城</span>
<a href="pages/order/order.jsp">我的订单</a>
<a href="index.jsp">注销</a>
</e:if>
<a href="pages/cart/cart.jsp">购物车</a>
<a href="pages/manager/manager.jsp">后台管理</a>
</div>
注销用户
1、销毁Session 中用户登录的信息(或者销毁Session)
2、重定向到首页(或登录页面)
UserServlet 程序中添加logout 方法:
protected void logOut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 注销Session
req.getSession().invalidate();
// 重定向
resp.sendRedirect(req.getContextPath());
}
修改【注销】的菜单地址:
<a href="userServlet?action=logout">注销</a>
验证码
表单重复提交
表单重复提交有三种常见的情况:
-
提交完表单。服务器使用请求转来进行页面跳转。这个时候,用户按下功能键F5,就会发起最后一次的请求。
造成表单重复提交问题。- 解决方法:使用重定向来进行跳转!
-
用户正常提交服务器,但是由于网络延迟等原因,迟迟未收到服务器的响应,这个时候,用户以为提交失败,
就会着急,然后多点了几次提交操作,也会造成表单重复提交。 -
用户正常提交服务器。服务器也没有延迟,但是提交完成后,用户回退浏览器,重新提交。也会造成表单重复
提交。
以上便是三种表单重复提交的情况,第一种可以通过重定向来解决,而后面两种则可以通过验证码来解决!
验证码防止表单重复提交
当用户使用客户端访问服务器时,访问regist.jsp页面,如果这是用户第一次登录,则生成一个随机的验证码并把验证码保存在Session域中。
当用户输入用户名和密码并进行表单提交至RegistServlet程序时,该程序会先获取表达Session中保存的验证码,并且将Session中的验证码进行删除,并获取表单中的信息,判断表单中的验证码信息和Session信息是否相等,相等则允许操作,不相等则组织操作!
谷歌验证码kaptcha 使用步骤如下:
1、导入谷歌验证码的jar 包:kaptcha-2.3.2.jar
2、在web.xml 中去配置用于生成验证码的Servlet 程序
<servlet>
<servlet-name>KaptchaServlet</servlet-name>
<servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>KaptchaServlet</servlet-name>
<url-pattern>/kaptcha.jpg</url-pattern>
</servlet-mapping>
3、在表单中使用img标签去显示验证码图片并使用它
<form action="http://localhost:8080/tmp/registServlet" method="get">
用户名:<input type="text" name="username" > <br>
验证码:<input type="text" style="width: 80px;" name="code">
<img src="http://localhost:8080/tmp/kaptcha.jpg" alt="" style="width: 100px; height: 28px;"> <br>
<input type="submit" value="登录">
</form>
4、在服务器获取谷歌生成的验证码和客户端发送过来的验证码比较使用
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
// 获取Session 中的验证码
String token = (String) req.getSession().getAttribute(KAPTCHA_SESSION_KEY);
// 删除 Session 中的验证码
req.getSession().removeAttribute(KAPTCHA_SESSION_KEY);
String code = req.getParameter("code");
// 获取用户名
String username = req.getParameter("username");
if (token != null && token.equalsIgnoreCase(code)) {
System.out.println("保存到数据库:" + username);
resp.sendRedirect(req.getContextPath() + "/ok.jsp");
} else {
System.out.println("请不要重复提交表单");
}
}
切换验证码:
// 给验证码的图片,绑定单击事件
$("#code_img").click(function () {
// 在事件响应的function 函数中有一个this 对象。这个this 对象,是当前正在响应事件的dom 对象
// src 属性表示验证码img 标签的 图片路径。它可读,可写
// alert(this.src);
this.src = "${basePath}kaptcha.jpg?d=" + new Date();
});