JavaWeb尚硅谷网上书城项目总结(上)
- 第一阶段
- 1.1 表单校验
- 第二阶段——用户模块
- 2.1 JavaEE项目的三层架构(重点)
- 2.2 代码编写流程
- 2.3 实现用户注册功能
- 第三阶段——代码的优化和抽取
- 3.1 jsp页面的抽取
- 3.2 表单回显——即登录,注册错误提示
- 3.3 BaseServlet的抽取(重点)
- 3.4 数据的封装和抽取BeanUtils的使用(重点)
- 第四阶段——图书模块
- 4.1 MVC概念
- 4.2 图书列表管理页面的实现
- 4.3 添加图书的功能——跳转使用重定向(重点)
- 4.4 给删除添加确认提示操作:
- 4.5 修改图书功能的实现
- 4.6 同一页面如何即执行添加操作又执行修改操作(重点)
- 第五阶段——图书分页(重点)
- 5.1 分页的初步实现
- 5.2 首页、上一页、下一页、末页和跳转到指定页面的实现
- 5.3 分页模块中 ,页码处理
- 5.4 修改分页后,增加,删除,修改图书信息的回显页面
- 5.5 分页条的抽取(难点)
- 第六阶段——验证码
- 6.1 显示登录的用户信息
- 6.2 登出——注销用户
- 6.3 表单重复提交之——验证码(重点)
- 6.4 谷歌 kaptcha 图片验证码的使用
第一阶段
1.1 表单校验
注册表单校验的实现:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>尚硅谷会员注册页面</title>
<%--静态包含 base标签,css样式,jQuery--%>
<%@ include file="../common/head.jsp" %>
<style type="text/css">
.login_form{
height:420px;
margin-top: 25px;
}
</style>
<!-- 验证用户名:必须由字母,数字下划线组成,并且长度为 5 到 12 位-->
<!-- 验证密码:必须由字母,数字下划线组成,并且长度为 5 到 12 位-->
<!-- 验证确认密码:和密码相同-->
<!-- 邮箱验证:xxxxx@xxx.com-->
<!-- 验证码:现在只需要验证用户已输入。因为还没讲到服务器。验证码生成。-->
<script type="text/javascript">
$(function(){
$("#sub_btn").click(function(){
var username = $("#username").val();
var usernamePatt = /^\w{5,12}$/;
if(!usernamePatt.test(username)){
$("span.errorMsg").html("用户名输入不合法");
return false;
}
//验证密码
var password = $("#password").val();
var passwordPatt = /^\w{5,12}$/;
if(!passwordPatt.test(password)){
$("span.errorMsg").html("密码输入不合法");
return false;
}
//验证密码输入是否相同
var repwd = $("#repwd").val();
if(password != repwd){
$("span.errorMsg").html("密码输入不一致");
return false;
}
//验证邮箱
var email = $("#email").val();
var emailPatt = /^[a-z\d]+(\.[a-z\d]+)*@([\da-z](-[\da-z])?)+(\.{1,2}[a-z]+)+$/;
if(!emailPatt.test(email)){
$("span.errorMsg").html("邮箱输入不合法");
return false;
}
$("span.errorMsg").html("");
//验证码
var code = $("#code").val();
//去除字符串的前后空格
code = $.trim(code);
if(code == "" || code == null){
$("span.errorMsg").html("验证码输入不合法");
return false;
}
//进行插入操作
});
});
</script>
</head>
<body>
<div id="login_header">
<img class="logo_img" alt="" src="static/img/logo.gif" >
</div>
<div class="login_banner">
<div id="l_content">
<span class="login_word">欢迎注册</span>
</div>
<div id="content">
<div class="login_form">
<div class="login_box">
<div class="tit">
<h1>注册尚硅谷会员</h1>
<span class="errorMsg">
<%=request.getAttribute("msg")==null?"":request.getAttribute("msg")%>
</span>
</div>
<div class="form">
<form action="userServlet" method="post">
<input type="hidden" name="action" value="regist"/>
<label>用户名称:</label>
<input class="itxt" type="text" placeholder="请输入用户名"
autocomplete="off" tabindex="1" name="username" id="username"
value="${requestScope.username}" />
<br />
<br />
<label>用户密码:</label>
<input class="itxt" type="password" placeholder="请输入密码"
autocomplete="off" tabindex="1" name="password" id="password" value="123456"/>
<br />
<br />
<label>确认密码:</label>
<input class="itxt" type="password" placeholder="确认密码"
autocomplete="off" tabindex="1" name="repwd" id="repwd" value="123456"/>
<br />
<br />
<label>电子邮件:</label>
<input class="itxt" type="text" placeholder="请输入邮箱地址"
autocomplete="off" tabindex="1" name="email" id="email"
value="${requestScope.email}" />
<br />
<br />
<label>验证码:</label>
<input class="itxt" type="text" style="width: 80px;" id="code" name="code"/>
<img id="code_img" src="kaptcha.jpg" style="float: right; margin-right: 40px;width:100px;height:28px">
<br />
<br />
<input type="submit" value="注册" id="sub_btn" />
</form>
</div>
</div>
</div>
</div>
</div>
<%--静态包含页脚内容--%>
<%@ include file="/pages/common/footer.jsp" %>
</body>
</html>
第二阶段——用户模块
2.1 JavaEE项目的三层架构(重点)
分层的目的是为了解耦。解耦就是为了降低代码的耦合度。方便项目后期的维护和升级。
web 层 com.zb.web/servlet/controller
service 层 com.zb.service Service接口包
com.zb.service.impl Service接口实现类
dao 持久层 com.zb.dao Dao接口包
com.zb.dao.impl Dao接口实现类
实体 bean 对象 com.zb.pojo/entity/domain/bean JavaBean 类
测试包 com.zb.test/junit
工具类 com.zb.utils
2.2 代码编写流程
1、先创建书城需要的数据库和表
2、编写数据库表中对应的javaBean对象
3、编写工具类JDBCUtils并测试
4、编写 BaseDao和与负责数据库交互的Dao
5、编写Service层
6、编写web层(servlet)
JDBCUtils 工具类:
public class JDBCUtils {
private static DruidDataSource dataSource;
static {
try {
Properties properties = new Properties();
// 读取 jdbc.properties 属性配置文件
InputStream inputStream = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
// 从流中加载数据
properties.load(inputStream);
// 创建 数据库连接 池
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取数据库连接池中的连接
* @return 如果返回 null, 说明获取连接失败 <br/> 有值就是获取连接成功
*/
public static Connection getConnection(){
Connection conn = null;
try {
conn = dataSource.getConnection();
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
/**
* 关闭连接,放回数据库连接池
* @param conn
*/
public static void close(Connection conn){
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
编写BaseDao
public abstract class BaseDao {
private QueryRunner queryRunner = new QueryRunner();
/**
* update() 方法用来执行 insert/update/delete语句
* @param sql
* @param args
* @return 如果返回-1,说明执行失败,返回其他表示受影响的行数
*/
public int update(String sql,Object...args){
System.out.println("OrderDaoImpl程序在【"+Thread.currentThread()+"】");
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
return queryRunner.update(conn,sql,args);
} catch (SQLException e) {
e.printStackTrace();
}finally{
JDBCUtils.close(conn);
}
return -1;
}
/**
* 查询返回一个javaBean的sql语句
* @param type 返回的对象类型
* @param sql 执行的sql语句
* @param args sql对应的参数值
* @param <T> 返回的类型的泛型
* @return
*/
public <T> T queryForOne(Class<T> type,String sql,Object...args){
Connection conn = null;
try{
conn = JDBCUtils.getConnection();
return queryRunner.query(conn,sql,new BeanHandler<T>(type),args);
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.close(conn);
}
return null;
}
/**
* 查询返回多个javaBean的sql语句
* @param type 返回的对象类型
* @param sql 执行的sql语句
* @param args sql对应的参数值
* @param <T> 返回的类型的泛型
* @return
*/
public <T> List<T> queryForList(Class<T> type, String sql, Object...args){
Connection conn = null;
try{
conn = JDBCUtils.getConnection();
return queryRunner.query(conn,sql,new BeanListHandler<T>(type),args);
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.close(conn);
}
return null;
}
/**
* 执行返回一行一列的sql语句
* @param sql 执行的sql语句
* @param args sql对应的参数值
* @return
*/
public Object queryForSingleValue(String sql,Object...args){
Connection conn = null;
try{
conn = JDBCUtils.getConnection();
return queryRunner.query(conn,sql,new ScalarHandler(),args);
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.close(conn);
}
return null;
}
}
2.3 实现用户注册功能
编写注册的servlet程序:
protected void regist(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取session中的验证码
String token = (String)req.getSession().getAttribute(KAPTCHA_SESSION_KEY);
//删除Session中的验证码
req.getSession().removeAttribute(KAPTCHA_SESSION_KEY);
//1.获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
String email = req.getParameter("email");
String code = req.getParameter("code");
User user = WebUtils.copyParamToBean(req.getParameterMap(),new User());
System.out.println(user);
//2.检查验证码是否正确
if(token!= null && token.equals(code)){
//3.检查用户名是否存在
if(userService.existsUsername(username)){
//用户名存在
//把回显信息,保存到request域中
req.setAttribute("msg","用户名已存在");
req.setAttribute("username",username);
req.setAttribute("email",email);
//跳回注册页面
req.getRequestDispatcher("/pages/user/regist.jsp").forward(req,resp);
}else{
//调用service保存到数据库
userService.registUser(new User(null,username,password,email));
//跳到注册成功页面regist_success.html
req.getRequestDispatcher("/pages/user/regist_success.jsp").forward(req,resp);
}
}else{
//验证码错误
//把回显信息,保存到request域中
req.setAttribute("msg","验证码错误");
req.setAttribute("username",username);
req.setAttribute("email",email);
System.out.println("验证码["+code+"]错误");
req.getRequestDispatcher("/pages/user/regist.jsp").forward(req,resp);
}
}
第三阶段——代码的优化和抽取
3.1 jsp页面的抽取
首先:添加 base 标签,固定相对路径跳转的结果
<!-- 写 base 标签,永远固定相对路径跳转的结果 -->
<base href="http://localhost:8080/book/">
将head中 css,jquery,base 、每个页面的页脚、以及不同页面的公共页面进行提取。然后使用静态包含的方法引入提取出来的文件。
例如:
提取之前:提取之后:
3.2 表单回显——即登录,注册错误提示
3.3 BaseServlet的抽取(重点)
在实际的项目开发中,一个模块,一般只使用一个 Servlet 程序
- 使用反射优化大量 else if
使用反射之前:使用反射通过action参数动态的调用登录或者注册的业务反射代码实例:@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //解决post请求中文乱码问题 resp.setContentType("text/html; charset=UTF-8"); //解决响应的中文乱码 req.setCharacterEncoding("UTF-8"); String action = req.getParameter("action"); try { //获取action业务鉴别字符串,获取相应的业务(方法反射对象) Method method = this.getClass().getDeclaredMethod(action,HttpServletRequest.class,HttpServletResponse.class); //调用目标业务 method.invoke(this,req,resp); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e);//把异常抛给Filter过滤器 } }
- 抽取BaseServlet程序
如果每个servlet都要写反射代码还是有点麻烦,所以把反射代码抽取出来,以后新的servlet只需要继承BaseServlet即可BaseServlet代码:
public abstract class BaseServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //解决post请求中文乱码问题 resp.setContentType("text/html; charset=UTF-8"); //解决响应的中文乱码 req.setCharacterEncoding("UTF-8"); String action = req.getParameter("action"); try { //获取action业务鉴别字符串,获取相应的业务(方法反射对象) Method method = this.getClass().getDeclaredMethod(action,HttpServletRequest.class,HttpServletResponse.class); //调用目标业务 method.invoke(this,req,resp); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e);//把异常抛给Filter过滤器 } } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req,resp); } }
3.4 数据的封装和抽取BeanUtils的使用(重点)
- BeanUtils 工具类,它可以一次性的把所有请求的参数注入到 JavaBean 中。
- BeanUtils 工具类,经常用于把 Map 中的值注入到 JavaBean 中,或者是对象属性值的拷贝操作。
BeanUtils的使用:
1、导入需要的 jar 包:
commons-beanutils-1.8.0.jar
commons-logging-1.1.1.jar
2、调用populate()方法
populate(Object bean, Map<String,String[]>properties)
将Map数据封装到指定Javabean中,一般用于将表单的所有数据封装到javabean
使用前提:参数名称 需要和javabean的属性名称保持一致
BeanUtils的使用提取成WebUtils 工具类:
第四阶段——图书模块
4.1 MVC概念
- MVC 全称:Model 模型、 View 视图、 Controller 控制器。
- MVC 最早出现在 JavaEE 三层中的 Web 层,它可以有效的指导 Web 层的代码如何有效分离,单独工作。
- View 视图:只负责数据和界面的显示,不接受任何与显示数据无关的代码,便于程序员和美工的分工合作——JSP/HTML。
- Controller 控制器:只负责接收请求,调用业务层的代码处理请求,然后派发页面,是一个“调度者”的角色——Servlet。转到某个页面。或者是重定向到某个页面。
- Model 模型:将与业务逻辑相关的数据封装为具体的 JavaBean 类,其中不掺杂任何与数据处理相关的代码——JavaBean/domain/entity/pojo。
MVC是一种思想
MVC 的理念是将软件代码拆分成为组件,单独开发,组合使用( 目的还是为了降低耦合度)。
MVC 的作用还是为了降低耦合。让代码合理分层。方便后期升级和维护。
4.2 图书列表管理页面的实现
如果访问jsp无法直接得到数据,name可以先让程序访问Servlet程序,再请求转发
BookServlet 程序中添加list方法:
protected void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1 通过BookService查询全部图书
List<Book> books = bookService.queryBooks();
//2 把全部图书保存到Request域中
req.setAttribute("books",books);
//3 请求转发到/pages/manager/book_manager.jsp页面
req.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(req,resp);
}
pages/manager/book_manager.jsp页面的数据遍历输出:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>图书管理</title>
<%--静态包含 base标签,css样式,jQuery--%>
<%@ include file="../common/head.jsp" %>
<script type="text/javascript">
$(function(){
// 给删除的a标签绑定单击事件,用于删除的确认提示操作
$("a.deleteClass").click(function(){
// 在事件的function函数中,有一个this对象。这个this对象,是当前正在响应事件的dom对象。
/**
* confirm是确认提示框函数
* 参数是它的提示内容
* 它有两个按钮,一个确认,一个是取消。
* 返回true表示点击了,确认,返回false表示点击取消。
*/
return confirm("你确定要删除【"+$(this).parent().parent().find("td:first").text()+"】");
// return false// 阻止元素的默认行为===不提交请求
});
});
</script>
</head>
<body>
<div id="header">
<img class="logo_img" alt="" src="static/img/logo.gif" >
<span class="wel_word">图书管理系统</span>
<%--静态包含manager管理模块的菜单--%>
<%@ include file="../common/manager_menu.jsp" %>
</div>
<div id="main">
<table>
<tr>
<td>名称</td>
<td>价格</td>
<td>作者</td>
<td>销量</td>
<td>库存</td>
<td colspan="2">操作</td>
</tr>
<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}&pageNo=${requestScope.page.pageNo}">修改</a></td>
<td><a class="deleteClass" href="manager/bookServlet?action=delete&id=${book.id}&pageNo=${requestScope.page.pageNo}">删除</a></td>
</tr>
</c:forEach>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td><a href="pages/manager/book_edit.jsp?pageNo=${requestScope.page.pageTotal}">添加图书</a></td>
</tr>
</table>
<%--静态包含分页条--%>
<%@include file="/pages/common/page_nav.jsp" %>
</div>
<%--静态包含页脚内容--%>
<%@ include file="../common/footer.jsp" %>
</body>
</html>
4.3 添加图书的功能——跳转使用重定向(重点)
图解分析:
问题说明:如果使用请求转发会出现表单重复提交
当用户提交完请求,浏览器会记录下最后一次请求的全部信息。当用户按下功能键 F5,就会发起浏览器记录的最后一次请求。
解决办法:使用重定向
4.4 给删除添加确认提示操作:
给删除添加class属性
javaScript语法:
<script type="text/javascript">
$(function(){
// 给删除的a标签绑定单击事件,用于删除的确认提示操作
$("a.deleteClass").click(function(){
// 在事件的function函数中,有一个this对象。这个this对象,是当前正在响应事件的dom对象。
/**
* confirm是确认提示框函数
* 参数是它的提示内容
* 它有两个按钮,一个确认,一个是取消。
* 返回true表示点击了,确认,返回false表示点击取消。
*/
return confirm("你确定要删除【"+$(this).parent().parent().find("td:first").text()+"】");
// return false// 阻止元素的默认行为===不提交请求
});
});
</script>
4.5 修改图书功能的实现
4.6 同一页面如何即执行添加操作又执行修改操作(重点)
book_edit.jsp页面,即要做添加操作,又要做修改操作。所以如何动态修改隐藏域<input type=“hidden” name=“action” value=“xxx” />让它的值即可以实现添加,又可以实现修改操作
- 解决方案一
可以发请求发起时,附带上当前要操作的值,并注入到隐藏域中。 - 解决方案二
可以通过判断当前请求参数中是否包含有id参数。如果有说明修改操作。如果没有说明是添加操作。${empty param.id ? “add”:“update”} - 解决方案三
可以通过判断,Request域中是否包含有修改的图书信息对象,如果没有说明是添加操作,如果有说明是修改操作。
第五阶段——图书分页(重点)
分页模块的分析:
5.1 分页的初步实现
- 抽取分页模型Page
- BookDao代码:
@Override
public int queryBookTotalCount() {
String sql = "select count(*) from t_book";
String strInt = String.valueOf(queryForSingleValue(sql));
int count = Integer.parseInt(strInt);
return count;
}
@Override
public List<Book> queryPageItems(int begin, int pageSize) {
String sql = "select id,name,author,price,sales,stock,img_path imgPath from t_book limit ?,?";
return queryForList(Book.class,sql,begin,pageSize);
}
- BookService代码:
@Override
public Page page(int pageNo, int pageSize) {
Page<Book> page = new Page();
// 设置每页显示的数量
page.setPageSize(pageSize);
// 求总记录数
int pageTotalCount = bookDao.queryBookTotalCount();
// 设置总记录数
page.setPageTotalCount(pageTotalCount);
// 求总页码
int pageTotal = pageTotalCount/pageSize + (pageTotalCount%pageSize==0?0:1);
// 设置总页码
page.setPageTotal(pageTotal);
// 设置当前页码
page.setPageNo(pageNo);
// 求当前页数据的开始索引
int begin = (page.getPageNo()-1)*pageSize;
// 求当前页数据
List<Book> items = bookDao.queryPageItems(begin,pageSize);
// 设置当前页数据
page.setItems(items);
return page;
}
- BookServlet 程序的代码:
protected void page(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1 获取请求的参数 pageNo 和 pageSize
int pageNo = WebUtils.parseInt(req.getParameter("pageNo"),1);
// int pageSize = WebUtils.parseInt(req.getParameter("pageSize"), Page.PAGE_SIZE);
int pageSize = Page.PAGE_SIZE;
//2 调用BookService.page(pageNo,pageSize):Page对象
Page<Book> page = bookService.page(pageNo,pageSize);
page.setUrl("manager/bookServlet?action=page");
//3 保存Page对象到Request域中
req.setAttribute("page",page);
//4 请求转发到pages/manager/book_manager.jsp页面
req.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(req,resp);
}
5.2 首页、上一页、下一页、末页和跳转到指定页面的实现
- 首页、上一页、下一页、末页的实现
<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>
</c:if>
<a href="#">3</a>
【${ requestScope.page.pageNo }】
<a href="#">5</a>
<%-- 如果已经 是最后一页,则不显示下一页,末页 --%>
<c:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}">
<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="4" name="pn" id="pn_input"/>页
<input type="button" value=" 确定">
</div>
- 实现跳转到指定页面:
- 注意:Page 对象中的修改:
public void setPageNo(Integer pageNo) {
/* 数据边界的有效检查 */
if (pageNo < 1) {
pageNo = 1;
}
if (pageNo > pageTotal) {
pageNo = pageTotal;
}
this.pageNo = pageNo;
}
5.3 分页模块中 ,页码处理
需求:显示 5 个连续的页码,而且当前页码在中间。除了当前页码之外,每个页码都可以点击跳到指定页。
情况1 :如果总页码小于等于 5 的情况,页码的范围是:1- 总页码
1 页 1
2 页 1,2
3 页 1,2,3
4 页 1,2,3,4
5 页 1,2,3,4,5
情况 2 :总页码大于 5 的情况。假设一共 10 页 页
小情况 1 :当前页码为前面 3 个:1 ,2 ,3 的情况,页码范围是:1-5.
【1】2,3,4,5
1【2】3,4,5
1,2【3】4,5
小情况 2 :当前页码为最后 3 个,8 ,9 ,10 ,页码范围是:总页码减 4 - 总页码
6,7【8】9,10
6,7,8【9】10
6,7,8,9【10】
小情况 3 :4 ,5 ,6 ,7 ,页码范围是:当前页码减 2 - 当前页码加 2
2,3【4】5,6
3,4【5】6,7
4,5【6】7,8
5,6【7】8,9
解决代码:
<%--页码输出的开始--%>
<c:choose>
<%--情况1:如果总页码小于等于5的情况,页码的范围是:1-总页码--%>
<c:when test="${requestScope.page.pageTotal <= 5}">
<c:set var="begin" value="1"/>
<c:set var="end" value="${requestScope.page.pageTotal}"/>
</c:when>
<%--情况2:总页码大于5的情况--%>
<c:when test="${requestScope.page.pageTotal>5}">
<c:choose>
<%--小情况1:当前页码为前面3个:1,2,3的情况,页码范围是:1-5.--%>
<c:when test="${requestScope.page.pageNo<=3}">
<c:set var="begin" value="1"/>
<c:set var="end" value="5"/>
</c:when>
<%--小情况2:当前页码为最后3个,8,9,10,页码范围是:总页码减4 - 总页码--%>
<c:when test="${requestScope.page.pageNo > requestScope.page.pageTotal-3}">
<c:set var="begin" value="${requestScope.page.pageTotal-4}"/>
<c:set var="end" value="${requestScope.page.pageTotal}"/>
</c:when>
<%--小情况3:4,5,6,7,页码范围是:当前页码减2 - 当前页码加2--%>
<c:otherwise>
<c:set var="begin" value="${requestScope.page.pageNo-2}"/>
<c:set var="end" value="${requestScope.page.pageNo+2}"/>
</c:otherwise>
</c:choose>
</c:when>
</c:choose>
<c:forEach begin="${begin}" end="${end}" var="i">
<c:if test="${requestScope.page.pageNo == i}">
【${i}】
</c:if>
<c:if test="${requestScope.page.pageNo != i}">
<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
</c:if>
</c:forEach>
5.4 修改分页后,增加,删除,修改图书信息的回显页面
以修改图书为示例:
- 在修改的请求地址上追加当前页码参数:
- 在 book_edit.jsp 页面中使用隐藏域记录下 pageNo 参数
- 在服务器重定向的时候,获取当前页码追加上进行跳转
5.5 分页条的抽取(难点)
- 在page对象中添加 url
- 在Servlet 程序的page分页方法中设置 url
- 修改分页条中请求地址为url变量输出,并抽取一个单独的 jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--分页条的开始--%>
<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>
</c:if>
<%--页码输出的开始--%>
<c:choose>
<%--情况1:如果总页码小于等于5的情况,页码的范围是:1-总页码--%>
<c:when test="${requestScope.page.pageTotal <= 5}">
<c:set var="begin" value="1"/>
<c:set var="end" value="${requestScope.page.pageTotal}"/>
</c:when>
<%--情况2:总页码大于5的情况--%>
<c:when test="${requestScope.page.pageTotal>5}">
<c:choose>
<%--小情况1:当前页码为前面3个:1,2,3的情况,页码范围是:1-5.--%>
<c:when test="${requestScope.page.pageNo<=3}">
<c:set var="begin" value="1"/>
<c:set var="end" value="5"/>
</c:when>
<%--小情况2:当前页码为最后3个,8,9,10,页码范围是:总页码减4 - 总页码--%>
<c:when test="${requestScope.page.pageNo > requestScope.page.pageTotal-3}">
<c:set var="begin" value="${requestScope.page.pageTotal-4}"/>
<c:set var="end" value="${requestScope.page.pageTotal}"/>
</c:when>
<%--小情况3:4,5,6,7,页码范围是:当前页码减2 - 当前页码加2--%>
<c:otherwise>
<c:set var="begin" value="${requestScope.page.pageNo-2}"/>
<c:set var="end" value="${requestScope.page.pageNo+2}"/>
</c:otherwise>
</c:choose>
</c:when>
</c:choose>
<c:forEach begin="${begin}" end="${end}" var="i">
<c:if test="${requestScope.page.pageNo == i}">
【${i}】
</c:if>
<c:if test="${requestScope.page.pageNo != i}">
<a href="${requestScope.page.url}&pageNo=${i}">${i}</a>
</c:if>
</c:forEach>
<%--如果已经是最后一页,则不显示下一页,末页--%>
<c:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}">
<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="${requestScope.page.pageNo}" name="pn" id="pn_input"/>页
<input type="button" id="searchPageBtn" value="确定" >
<%--跳转到指定页数功能--%>
<script type="text/javaScript">
$(function(){
$("#searchPageBtn").click(function(){
var pageNo = $("#pn_input").val();
<%--var pageTotal = ${requestScope.page.pageTotal};--%>
<%--alert(pageTotal);--%>
// javaScript语言中提供了一个location地址栏对象
// 它有一个属性叫href.它可以获取浏览器地址栏中的地址
// href属性可读,可写,
// 赋值操作,location.href=”/url” 表示当前页面打开URL页面
// 读操作,表示获取当前地址栏的url
location.href = "${requestScope.basePath}${requestScope.page.url}&pageNo="+pageNo;
});
});
</script>
</div>
<%--分页条的结束--%>
第六阶段——验证码
6.1 显示登录的用户信息
登录成功之后将用户登录的信息保存到session中
UserServlet 程序中保存用户登录的信息:
页面由登录注册改为显示登录的用户信息:
6.2 登出——注销用户
1、销毁 Session 中用户登录的信息(或者销毁 Session)
2、重定向到首页(或登录页面)
UserServlet 程序中添加 logout 方法:
/**
* 注销
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void logout(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1、销毁 Session 中用户登录的信息(或者销毁 Session)
req.getSession().invalidate();
//2、重定向到首页(或登录页面)
resp.sendRedirect(req.getContextPath());
}
6.3 表单重复提交之——验证码(重点)
表单重复提交有三种常见的情况:
一:提交完表单。服务器使用请求转来进行页面跳转。这个时候,用户按下功能键 F5,就会发起最后一次的请求。造成表单重复提交问题。解决方法:使用重定向来进行跳转
二:用户正常提交服务器,但是由于网络延迟等原因,迟迟未收到服务器的响应,这个时候,用户以为提交失败,就会着急,然后多点了几次提交操作,也会造成表单重复提交。
三:用户正常提交服务器。服务器也没有延迟,但是提交完成后,用户回退浏览器。重新提交。也会造成表单重复提交。
6.4 谷歌 kaptcha 图片验证码的使用
谷歌验证码 kaptcha 使用步骤如下:
- 导入谷歌验证码的 jar 包
kaptcha-2.3.2.jar - 在 web.xml 中去配置用于生成验证码的 Servlet 程序
com.google.code.kaptcha.servlet.KaptchaServlet,生成一个图片。同时将生成的验证码字符串放到HttpSession中
<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>
- 在表单中使用 img 标签去显示验证码图片并使用它
<form method="get" action="http://localhost:8080/tmp/loginServlet">
用户名:<input type="text" name="username" /><br/>
验证码:<input type="text" name="code" style="width:50px"/>
<img src="/tmp/kaptcha.jpg" style="width:100px;height:28px;"><br/>
<input type="submit" value="登录"/>
</form>
- 在服务器获取谷歌生成的验证码和客户端发送过来的验证码比较使用
@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("/tmp/ok.jsp");
}else{
System.out.println("请不要重复提交表单");
}
}
- 切换验证码的实现(重点)
//给验证码的图片,绑定单击事件
$("#code_img").click(function(){
// 在事件响应的 function 函数中有一个 this 对象。这个 this 对象,是当前正在响应事件的 dom 对象
// src 属性表示验证码 img 标签的 图片路径。它可读,可写
// alert(this.src);
this.src = "kaptcha.jpg?d="+new Date();//在后面加上一个每次都不同的值
});