项目实战系列三: 家居购项目 第三部分
文章目录
- 🍃后台分页
- 🍅后台分页导航
- 🍃首页分页
- 🍅首页分页导航
- 🍅首页搜索
- 🍅两个奇怪的问题
- 🍅会员显示登录名
- 🍅注销登录
- 🍅验证码
🍃后台分页
程序框架图
1.新建com.zzw.furns.entity.Page
//Page是一个Javabean, 是一个分页的数据模型(包含了分页的各种信息)
//T表示泛型, 因为将来分页模型对应的数据类型是不确定的
public class Page<T> {//因为每页显示多少条记录, 是其它地方也可以使用的public static final Integer PAGE_SIZE = 3;//pageNo 前端页面传来的private Integer pageNo;//表示显示当前页[即显示第几页]private Integer pageSize = PAGE_SIZE;//表示每页显示几条记录//pageTotal 计算得到的private Integer pageTotal;//表示共有多少页//totalRow 是可以从数据库来的, 由DAO来完成private Integer totalRow;//表示共有多少条记录//items 从数据库DB来的, 由DAO完成private List<T> items;//表示当前页,要显示的数据private String url;//分页导航的字符串public Page() {}public Page(Integer pageSize, Integer pageNo) {this.pageSize = pageSize;this.pageNo = pageNo;}
}
2.参考
3.修改FurnDao.java
//Page的哪些属性是可以从数据库中获取的, 就把获取这个属性的任务放在DAO层
public int getTotalRow();/*** 获取当前页要显示的数据** @param begin 表示当前数据从第几条记录开始获取, 从0开始计算* @param pageSize 表示取出多少条记录* @return*/
public List<Furn> getPageItems(int begin, int pageSize);
4.修改FurnDaoImpl.java
@Override
public int getTotalRow() {String sql = "SELECT COUNT(*) FROM furn";//return (Integer) queryScalar(sql);//=>会报cast异常//java.lang.Long cannot be cast to java.lang.Integerreturn ((Number) queryScalar(sql)).intValue();
}@Override
public List<Furn> getPageItems(int begin, int pageSize) {String sql = "select id, `name`,business,price,saleNum,inventory,image_path from furn limit ?,?";return queryMany(sql, Furn.class, begin, pageSize);
}
5.测试FurnDaoTest.java
@Test
public void getTotalRow() {int totalRow = furnDao.getTotalRow();System.out.println(totalRow);
}@Test
public void getPageItems() {List<Furn> pageItems = furnDao.getPageItems(1, 5);for (Furn furn : pageItems) {System.out.println(furn);}
}
6.修改FurnService.java
/*** 根据传入的 pageNo和 pageSize, 返回对应的page对象* @param pageNo* @param pageSize* @return*/
public Page<Furn> page(int pageNo, int pageSize);
7.修改FurnServiceImpl.java
@Override
public Page<Furn> page(int pageNo, int pageSize) {//先创建一个page对象, 然后根据实际情况, 填充属性Page<Furn> page = new Page<>();page.setPageNo(pageNo);//(1)表示显示当前页[即显示第几页]page.setPageSize(pageSize);//(2)表示每页显示几条记录int totalRow = furnDAO.getTotalRow();page.setTotalRow(totalRow);//(3)表示共有多少条记录//比如 6 2 => 6 / 2 = 3//比如 5 2 => 5 / 2 = 2//比如 7 2 => 7 / 2 = 3//比如 0 2 => 0 / 2 = 0int pageTotal = totalRow / pageSize;if (totalRow % pageSize > 0) {++pageTotal;}page.setPageTotal(pageTotal);//(4)表示共有多少页//比如: pageNo=1, pageSize=3 => begin=1//比如: pageNo=2, pageSize=3 => begin=4//比如: pageNo=3, pageSize=2 => begin=4//注意: 这里隐藏了一个坑, 此刻还看不到, 之后会暴露int begin = (pageNo - 1) * pageSize;List<Furn> pageItems = furnDAO.getPageItems(begin, pageSize);page.setItems(pageItems);//(5)表示当前页,要显示的数据//还差一个url[分页导航], 后面需要return page;
}
8.测试FurnServiceTest.java
@Test
public void page() {//小技巧: 如果我们需要看一个对象, 这个对象比较复杂, 可以debug看对象Page<Furn> page = furnService.page(2, 2);System.out.println("page ===> " + page);
}
9.修改src/com/zzw/furns/web/FurnServlet.java
, web层获取page对象
protected void page(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {int pageNo = DataUtils.parseInt(req.getParameter("pageNo"), 1);int pageSize = DataUtils.parseInt(req.getParameter("pageSize"), Page.PAGE_SIZE);//调用service, 获取page对象Page<Furn> page = furnService.page(pageNo, pageSize);//将page放入到request域req.setAttribute("page", page);//请求转发到家居管理页面req.getRequestDispatcher("/views/manage/furn_manage.jsp").forward(req, resp);
}
10.修改manage_menu.jsp
, 取缔list方法
<div class="header-bottom-set dropdown"><a href="manage/furnServlet?action=page">家居管理</a>
</div>
11.测试, 管理员登陆后, 点击家居管理
12.修改web/views/manage/furn_manage.jsp
<c:forEach items="${requestScope.page.items}" var="item">
<tr><td class="product-thumbnail"><a href="#"><img class="img-responsive ml-3" src="${item.imagePath}"alt=""/></a></td><td class="product-name"><a href="#">${item.name}</a></td><td class="product-name"><a href="#">${item.business}</a></td><td class="product-price-cart"><span class="amount">${item.price}</span></td><td class="product-quantity">${item.saleNum}</td><td class="product-quantity">${item.inventory}</td><td class="product-remove"><a href="manage/furnServlet?action=display&id=${item.id}"><i class="icon-pencil"></i></a><a furnName="${item.name}" href="manage/furnServlet?action=del&id=${item.id}"><i class="icon-close"></i></a></td>
</tr>
</c:forEach>
🍅后台分页导航
需求分析
- 管理员进入到家居管理后台页面
- 可以通过分页导航条来进行分页显示
- 完成上页, 下页, 显示共多少页
- 点击分页导航, 可以显示对应页的家居信息
- 在管理员进行修改, 删除, 添加后, 能够回显原来操作所在页面的数据
程序框架图
1.粘贴首页的分页导航代码
<!-- Pagination Area Start -->
<div class="pro-pagination-style text-center mb-md-30px mb-lm-30px mt-6" data-aos="fade-up"><ul><li><a href="#">首页</a></li><li><a href="#">上页</a></li><li><a class="active" href="#">3</a></li><li><a href="#">4</a></li><li><a href="#">5</a></li><li><a href="#">下页</a></li><li><a href="#">末页</a></li><li><a>共10页</a></li><li><a>共90记录</a></li></ul>
</div>
<!-- Pagination Area End -->
2.放到web/views/manage/furn_manage.jsp
, 并做修改
<!-- Pagination Area Start -->
<div class="pro-pagination-style text-center mb-md-30px mb-lm-30px mt-6" data-aos="fade-up"><ul><%--如果当前页 > 1, 就显示首页和上一页--%><li><a style="${requestScope.page.pageNo == 1 ? 'pointer-events: none; color: lightgray' : ''}"href="manage/furnServlet?action=page&pageNo=1&pageSize=${requestScope.page.pageSize}">首页</a></li><li><a style="${requestScope.page.pageNo == 1 ? 'pointer-events: none; color: lightgray' : ''}"href="manage/furnServlet?action=page&pageNo=${requestScope.page.pageNo - 1}&pageSize=${requestScope.page.pageSize}">上一页</a></li><%--显示所有的分页数 先确定开始的页数 begin 1; 再确定结束的页数 end=>pageTotal--%><%--最多显示10页, 这里涉及算法--%><c:set scope="page" var="begin" value="1"></c:set><c:set scope="page" var="end" value="${requestScope.page.pageTotal}"></c:set><%--循环显示--%><c:forEach begin="${pageScope.begin}" end="${pageScope.end}" var="i"><%--总的页数--%><%--如果i是当前页, 就使用class="active"来修饰--%><li><a class="${i eq requestScope.page.pageNo ? "active" : ""}"href="manage/furnServlet?action=page&pageNo=${i}&pageSize=${requestScope.page.pageSize}">${i}</a></li></c:forEach><%--如果当前页 < 总的页数, 就显示末页和下一页--%><li><a style="${requestScope.page.pageNo == requestScope.page.pageTotal ? 'pointer-events: none; color: lightgray' : ''}"href="manage/furnServlet?action=page&pageNo=${requestScope.page.pageNo + 1}&pageSize=${requestScope.page.pageSize}">下一页</a></li><li><a style="${requestScope.page.pageNo == requestScope.page.pageTotal ? 'pointer-events: none; color: lightgray' : ''}"href="manage/furnServlet?action=page&pageNo=${requestScope.page.pageTotal}&pageSize=${requestScope.page.pageSize}">末页</a></li><li><a>共${requestScope.page.pageTotal}页</a></li><li><a>共${requestScope.page.totalRow}记录</a></li></ul>
</div>
<!-- Pagination Area End -->
3.修改后返回原页面
4.删除后返回原页面
5.添加后返回原页面
🍃首页分页
🍅首页分页导航
需求分析
- 顾客进入首页页面
- 分页显示家居
- 正确显示分页导航条, 即功能完善, 可以使用
程序框架图
1.新建src/com/zzw/furns/web/CustomerFurnServlet.java
, 使用注解方式
@WebServlet(urlPatterns = "/customerFurnServlet")
public class CustomerFurnServlet extends BasicServlet {private FurnService furnService = new FurnServiceImpl();protected void page(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {Integer pageNo = DataUtils.parseInt(req.getParameter("pageNo"), 1);Integer pageSize = DataUtils.parseInt(req.getParameter("pageSize"), Page.PAGE_SIZE);Page<Furn> page = furnService.page(pageNo, pageSize);//保存到request域req.setAttribute("page", page);//请求转发到/views/customer/index.jspreq.getRequestDispatcher("/views/customer/index.jsp").forward(req, resp);}
}
2.将项目页面移到web/views/customer
目录下.
3.新建web/index.jsp
, 负责请求转发.
4.直接请求CustomerFurnServlet
, 获取网站首页要显示的分页数据. 类似我们网站的入口页面
5.分页显示数据
<c:forEach items="${requestScope.page.items}" var="item"><div class="col-lg-3 col-md-6 col-sm-6 col-xs-6 mb-6" data-aos="fade-up"data-aos-delay="200"><!-- Single Prodect --><div class="product"><div class="thumb"><a href="shop-left-sidebar.jsp" class="image"><img src="${item.imagePath}" alt="Product"/><img class="hover-image" src="assets/images/product-image/5.jpg"alt="Product"/></a><span class="badges"><span class="new">New</span></span><div class="actions"><a href="#" class="action wishlist" data-link-action="quickview"title="Quick view" data-bs-toggle="modal"data-bs-target="#exampleModal"><iclass="icon-size-fullscreen"></i></a></div><button title="Add To Cart" class=" add-to-cart">AddTo Cart</button></div><div class="content"><h5 class="title"><a href="shop-left-sidebar.jsp">Simple 北欧${item.name} </a></h5><span class="price"><span class="new">家居: ${item.name}</span></span><span class="price"><span class="new">厂商: ${item.business}</span></span><span class="price"><span class="new">价格: ¥${item.price}</span></span><span class="price"><span class="new">销量: ${item.saleNum}</span></span><span class="price"><span class="new">库存: ${item.inventory}</span></span></div></div></div>
</c:forEach>
6.首页分页导航
<c:set var="pageNo" value="${requestScope.page.pageNo}"></c:set>
<c:set var="pageTotal" value="${requestScope.page.pageTotal}"></c:set>
<c:set var="pageSize" value="${requestScope.page.pageSize}"></c:set>
<!-- Pagination Area Start -->
<div class="pro-pagination-style text-center mb-md-30px mb-lm-30px mt-6" data-aos="fade-up"><ul><li><a href="customerFurnServlet?action=page&pageNo=1&pageSize=${pageSize}">首页</a></li><li><a style="${pageNo <= 1 ? 'pointer-events: none; color: lightgray' : ''}" href="customerFurnServlet?action=page&pageNo=${pageNo - 1}&pageSize=${pageSize}">上页</a></li><c:forEach begin="1" end="${pageTotal}" var="i"><li><a class="${pageNo == i ? 'active' : ''}" href="customerFurnServlet?action=page&pageNo=${i}&pageSize=${pageSize}">${i}</a></li></c:forEach><li><a style="${pageNo >= pageTotal ? 'pointer-events: none; color: lightgray;': ''}" href="customerFurnServlet?action=page&pageNo=${pageNo + 1}&pageSize=${pageSize}">下页</a></li><li><a href="customerFurnServlet?action=page&pageNo=${pageTotal}&pageSize=${pageSize}">末页</a></li><li><a>共${pageTotal}页</a></li><li><a>共${requestScope.page.totalRow}记录</a></li></ul>
</div>
<!-- Pagination Area End -->
🍅首页搜索
需求分析
1.顾客进入首页页面
2.点击搜索按钮, 可以输入家居名
3.正确显示分页导航条, 并且要求在分页时, 保留上次搜索条件
程序框架图
代码实现
1.修改com.zzw.furns.FurnDAO
, 模糊查询👉
//根据name查询furn对象的个数
public int getTotalQuantityByName(String name);//根据begin,pageSize,name查询furn对象
public List<Furn> getPageItemsByName(int begin, int pageSize, String name);
2.修改com.zzw.furns.impl.FurnDaoImpl
@Override
public int getTotalQuantityByName(String name) {String sql = "SELECT COUNT(*) FROM furn WHERE `name` LIKE ?";return ((Number) queryScalar(sql, "%" + name + "%")).intValue();
}@Override
public List<Furn> getPageItemsByName(int begin, int pageSize, String name) {//mysql中, limit默认从0开始String sql = "SELECT id, `name`, business, price, saleNum, " +"inventory, image_path as imagePath FROM furn WHERE `name` LIKE ? Limit ?, ?";return queryMany(sql, Furn.class, "%" + name + "%", begin, pageSize);
}
3.测试, 修改FurnDaoTest
@Test
public void getTotalQuantityByName() {System.out.println(furnDAO.getTotalQuantityByName("小台灯"));
}@Test
public void getPageItemsByName() {List<Furn> furns = furnDAO.getPageItemsByName(3, 2, "小台灯");for (Furn furn : furns) {System.out.println(furn);}
}
4.修改com.zzw.furns.FurnService
//根据传入的pageNo,pageSize,name, 返回对应的page对象
public Page<Furn> pageByName(int pageNo, int pageSize, String name);
5.修改com.zzw.furns.impl.FurnServiceImpl
@Override
public Page<Furn> pageByName(int pageNo, int pageSize, String name) {//先创建一个page对象, 然后根据实际情况, 填充属性Page<Furn> page = new Page<>();page.setPageNo(pageNo);//(1)表示显示当前页[即显示第几页]page.setPageSize(pageSize);//(2)表示每页显示几条记录int countByName = furnDAO.getTotalQuantityByName(name);page.setTotalRow(countByName);//(3)根据家居名 返回总的记录数//比如 6 2 => 6 / 2 = 3//比如 5 2 => 5 / 2 = 2//比如 7 2 => 7 / 2 = 3//比如 0 2 => 0 / 2 = 0int pageTotal = countByName / pageSize;//计算得到总的页数if (countByName % pageSize > 0) {++pageTotal;}page.setPageTotal(pageTotal);//(4)表示共有多少页//比如: pageNo=1, pageSize=3 => begin=1//比如: pageNo=3, pageSize=2 => begin=4//注意: 这里隐藏了一个坑, 此刻还看不到, 之后会暴露int begin = (pageNo - 1) * pageSize;List<Furn> pageItems = furnDAO.getPageItemsByName(begin, pageSize, name);page.setItems(pageItems);//(5)表示当前页,要显示的数据//还差一个url[分页导航], 后面需要return page;
}
6.测试,修改com.zzw.furns.impl.FurnServiceTest
@Test
public void pageByName() {Page<Furn> page = furnService.pageByName("蚂蚁", 1, 5);System.out.println(page);
}
6.web层 - 修改src/com/zzw/furns/web/CustomerFurnServlet.java
, 增加pageByName
方法
protected void pageByName(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//获取家居名//1.如果参数有name, 但是没有值, 这时接收到的是空串, 查询所有家居信息//2.如果参数name都没有, 接收到的是nullString name = req.getParameter("name");name = (name == null) ? "" : name;int pageNo =DataUtils.parseInt(req.getParameter("pageNo"), 1);int pageSize =DataUtils.parseInt(req.getParameter("pageSize"), Page.PAGE_SIZE);//调用service方法, 获取page对象Page<Furn> page = furnService.pageByName(pageNo, pageSize, name);//将家居名[搜索条件]封装到page对象的url属性StringBuilder url = new StringBuilder("customerFurnServlet?action=pageByName");if (!"".equals(name)) {//如果name不为"", 则拼接name参数url.append("&name=").append(name);}page.setUrl(url.toString());//将page放入到request域req.setAttribute("page", page);//请求转发到index.jspreq.getRequestDispatcher("/views/customer/index.jsp").forward(req, resp);
}
7.修改web/views/customer/index.jsp
<div class="dropdown_search"><form class="action-form" action="customerFurnServlet"><input type="hidden" name="action" value="pageByName"><input type="hidden" name="pageSize" value="${param.pageSize}"><input class="form-control" name="name" placeholder="Enter your search key" type="text"><button class="submit" type="submit"><i class="icon-magnifier"></i></button></form>
</div>
8.修改web/views/customer/index.jsp
导航栏
<c:set var="pageNo" value="${requestScope.page.pageNo}"></c:set>
<c:set var="pageTotal" value="${requestScope.page.pageTotal}"></c:set>
<c:set var="pageSize" value="${requestScope.page.pageSize}"></c:set>
<c:set var="url" value="${requestScope.page.url}"></c:set>
<!-- Pagination Area Start -->
<div class="pro-pagination-style text-center mb-md-30px mb-lm-30px mt-6" data-aos="fade-up"><ul><li><a href="${url}&pageNo=1&pageSize=${pageSize}">首页</a></li><li><a style="${pageNo <= 1 ? 'pointer-events: none; color: lightgray' : ''}" href="${url}&pageNo=${pageNo - 1}&pageSize=${pageSize}">上页</a></li><c:forEach begin="1" end="${pageTotal}" var="i"><li><a class="${pageNo == i ? 'active' : ''}" href="${url}&pageNo=${i}&pageSize=${pageSize}">${i}</a></li></c:forEach><li><a style="${pageNo >= pageTotal ? 'pointer-events: none; color: lightgray;': ''}" href="${url}&pageNo=${pageNo + 1}&pageSize=${pageSize}">下页</a></li><li><a href="${url}&pageNo=${pageTotal}&pageSize=${pageSize}">末页</a></li><li><a>共${pageTotal}页</a></li><li><a>共${requestScope.page.totalRow}记录</a></li></ul>
</div>
<!-- Pagination Area End -->
🍅两个奇怪的问题
1.点击家居管理, 发出两个请求
从manage_menu.jsp
到furn_manage.jsp
抓包
原因: furn_manage.jsp
有图片url
2.首页分页出现问题
原因
🍅会员显示登录名
需求分析
1.会员登陆成功
2.如果登陆成功后返回首页面, 显示订单管理和安全退出
3.如果用户没有登陆过, 首页就显示登录注册, 后台管理超链接
程序框架图
1.修改src/com/zzw/furns/web/MemberServlet.java
if (memberService.login(member)) {//登陆成功System.out.println("登陆成功");//将member存入到session域request.getSession().setAttribute("member", member);if ("admin".equals(member.getUsername())) {//管理员登录成功request.getRequestDispatcher("/views/manage/manage_menu.jsp").forward(request, response);} else {//普通用户登陆成功request.getRequestDispatcher("/views/member/login_ok.jsp").forward(request, response);}
}
2.修改web/views/customer/index.jsp
<!-- Single Wedge Start -->
<c:if test="${sessionScope.member != null}"><div class="header-bottom-set dropdown">欢迎: ${sessionScope.member.username}</div><div class="header-bottom-set dropdown"><a href="#">订单管理</a></div><div class="header-bottom-set dropdown"><a href="#">安全退出</a></div>
</c:if>
<c:if test="${sessionScope.member == null}"><div class="header-bottom-set dropdown"><a href="views/member/login.jsp">登录|注册</a></div><div class="header-bottom-set dropdown"><a href="views/manage/manage_login.jsp">后台管理</a></div>
</c:if>
<!-- Single Wedge End -->
🍅注销登录
思路分析
1.用户登录成功后
2.login_ok.jsp, 点击安全退出, 注销登录
3.返回首页, 也可点击安全退出, 注销登录
程序框架图
代码实现
1.修改src/com/zzw/furns/web/MemberServlet.java
,增加方法logout
//安全退出
protected void logout(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("member 被调用...");HttpSession session = req.getSession();//让当前session立即无效session.invalidate();//默认访问index.jsp 首页面//req.getContextPath() http://localhost://jiaju_mall/resp.sendRedirect(req.getContextPath());
}
2.修改web/views/customer/index.jsp
<div class="header-bottom-set dropdown"><a href="memberServlet?action=logout">安全退出</a>
</div>
🍅验证码
表单重复提交情况
- 提交完表单. 服务器使用请求转发进行页面跳转. 用户刷新(F5), 会发起最后一次的请求, 造成表单重复提交问题. 解决:用重定向.
- 用户正常提交, 由于网络延迟等原因, 未收到服务器的响应. 这时, 用户着急多点了几次提交操作, 也会造成表单重复提交. 解决: 验证码
- 用户正常提交, 服务器也没有延迟, 但是提交完成后, 用户回退浏览器. 重新提交, 也会造成表单重复提交. 解决: 验证码
- 恶意注册, 使用可以批量发送Http的工具, 比如 Postman, Jemeter等, 使用验证码防护
程序框架图
代码实现
1.引入kaptcha-2.3.2.jar包
2.web层, 配置KaptchaServlet
<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>/kaptchaServlet</url-pattern>
</servlet-mapping>
3.KAPTCHA_SESSION_KEY是一个常量, 使用前需要导入
import static com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY;
4.修改前端页面web/views/member/login.jsp
<input type="text" id="captchaText" name="captcha" style="width: 50%"placeholder="验证码"/> <img id="captcha" alt="" width="150px" src="kaptchaServlet">
5.测试
6.增加js
代码, 点击图片更换验证码
$("#captcha").click(function () {$(this).attr("src", "kaptchaServlet?zzw=" + new Date());
});
7.增加校验 ⇒ 点击提交, 验证码不能为空
var captchaText = $("#captchaText").val().trim();
if (captchaText == "" || captchaText == null) {$("span.errorMsg").text("验证码不能为空!");return false;
}
8.修改MemberServlet
protected void register(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//todo 构建member//获取用户提交的验证码String captcha = request.getParameter("captcha");//从session中获取 生成的验证码HttpSession session = request.getSession();String token = (String) session.getAttribute(KAPTCHA_SESSION_KEY);//立即删除session中的验证码, 防止该验证码重复使用session.removeAttribute(KAPTCHA_SESSION_KEY);//如果token不为空, 并且和用户提交的验证码保持一致, 就继续if (token != null && token.equalsIgnoreCase(captcha)) {//todo 判断用户名是否可用 可用注册用户 不可用返回注册页面} else {request.setAttribute("msg", "验证码不正确");request.setAttribute("username", username);//回显用户名request.setAttribute("password", password);//回显密码request.setAttribute("email", email);//回显邮件request.setAttribute("active", "register_tab");request.getRequestDispatcher("/views/member/login.jsp").forward(request, response);}
}