【JAVA EE#5】【傳智書城·源碼閱讀】購物車模塊:購書流程+分頁顯示+自定義jsp標籤進行安全檢查+js倒計時簡單實現

購書流程
本模塊業務邏輯:
在這裏插入圖片描述
jsp文件分析
product_list.jsp是點擊分類默認顯示的佈局,而product_search_list.jsp是搜索結果頁面佈局,是內容主要是以下紅線框住部分,特點:分類條件顯示(只有product_list.jsp是,product_search_list.jsp該處統一“全部商品”)、動態顯示對應分類或者查詢圖書結果條數、動態顯示書名售價及封面、分頁顯示的實現。

product_list.jsp頁面顯示:
在這裏插入圖片描述
product_search_list.jsp頁面顯示:
在這裏插入圖片描述
因爲分類和搜索後都會返回一個PageBean到request儲存着,所以分類條件顯示、動態顯示對應分類或者查詢圖書結果條數這兩個功能用EL表達式直接就可以實現。

<h1>商品目錄</h1>
	<hr />
		<h1>${bean.category}</h1>&nbsp;&nbsp;&nbsp;&nbsp;共${bean.totalCount}種商品
	<hr />

動態顯示書名售價及封面,主要用了c標籤forEach語句遍歷輸出在Pagebeanproduct list列表,關於PageBean的數據結構,如何做到結果分頁顯示的,有待探討。

分頁顯示

<c:forEach items="${bean.ps}" var="p" varStatus="vs">
	<td>
		<div class="divbookpic">
			<p>
				<a href="${pageContext.request.contextPath}/findProductById?id=${p.id}">
				<img src="${pageContext.request.contextPath}${p.imgurl}" width="115" height="129" border="0" /> </a>									
			</p>
		</div>
		<div class="divlisttitle">
			<a href="${pageContext.request.contextPath}/findProductById?id=${p.id}">書名: ${p.name}<br />售價:¥${p.price} </a>
		</div>
	</td>
</c:forEach>

分頁顯示的實現:

<div class="pagination">
	<ul>
	<!-- 上一頁按鈕 -->
		<c:if test="${bean.currentPage!=1}">
		<li class="disablepage_p">
		<a class="disablepage_a" href="${pageContext.request.contextPath}/showProductByPage?currentPage=${bean.currentPage-1}&category=${bean.category}"></a>
		</li>
		</c:if>
	<!-- 中間按鈕 -->
		<c:if test="${bean.currentPage==1}">
		<li class="disablepage_p2"></li>
		</c:if>
		
		<c:forEach begin="1" end="${bean.totalPage}" var="pageNum">
		<c:if test="${pageNum==bean.currentPage}">
		<li class="currentpage">${pageNum }</li>
		</c:if>
		
		<c:if test="${pageNum!=bean.currentPage}">
		<li><a href="${pageContext.request.contextPath}/showProductByPage?currentPage=${pageNum}&category=${bean.category}">${pageNum}</a></li>
		</c:if>
		/c:forEach>

		<c:if test="${bean.currentPage==bean.totalPage||bean.totalPage==0}">
		<li class="disablepage_n2"></li>
		</c:if>
	
	<!-- 下一頁按鈕 -->
		<c:if test="${bean.currentPage!=bean.totalPage&&bean.totalPage!=0}">
		<li class="disablepage_n">
		<a class="disablepage_a" href="${pageContext.request.contextPath}/showProductByPage?currentPage=${bean.currentPage+1}&category=${bean.category}"></a></li>
		</c:if>
	</ul>
</div>

main.css中,例:

.pagination li.disablepage_p {
	width: 75px;
	height: 15px;
	padding: 5px;
	color: #929292;
	background:url(../images/previous_page.png) no-repeat center center;
}

product_list.jsp中“上一頁”的按鈕包含在樣式資源main.css中,該按鈕首先判斷是否爲第1頁,不是的話就執行訪問上一頁的操作,是的話就什麼都不做(不指定href)並且把樣式改成灰色的按鈕。然後進行循環遍歷設定中間按鈕,從第一頁到頁面總數最後一頁,再判斷是否爲當前PageNum(臨時變量,相當於for循環中的i)是否爲PageBean中儲存的當前頁currentPage是否一致,是的話將頁面樣式改成下移變暗的當前頁樣式,並且不指定動作。否則指定其動作,將PageNum作爲參數指定當前頁訪問showProductByPageServlet。再下來就是設定下一頁按鈕,最後一頁或者該Pagebean爲空頁時,變灰不可點選下一頁按鈕,如果當前頁不是最後一頁並且該Pagebean不爲空時,就指定正常的下一頁邏輯。
在這裏插入圖片描述
看看ProductService中具體的分頁操作:

// 分頁操作
	public PageBean findProductByPage(int currentPage, int currentCount,
			String category) {
		PageBean bean = new PageBean();
		// 封裝每頁顯示數據條數
		bean.setCurrentCount(currentCount);
		// 封裝當前頁碼
		bean.setCurrentPage(currentPage);
		// 封裝當前查找類別
		bean.setCategory(category);
		try {
			// 獲取總條數
			int totalCount = dao.findAllCount(category);
			bean.setTotalCount(totalCount);
			// 獲取總頁數
			int totalPage = (int) Math.ceil(totalCount * 1.0 / currentCount);
			bean.setTotalPage(totalPage);
			// 獲取當前頁數據
			List<Product> ps = dao.findByPage(currentPage, currentCount,
					category);
			bean.setPs(ps);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return bean;
	}

Math.ceil()爲 “向上取整”, 即小數部分直接捨去,並向正數部分進1。對比Math.round() (“四捨五入”, 該函數返回的是一個四捨五入後的的整數)以及Math.floor() (“向下取整” ,即小數部分直接捨去)爲什麼用這個函數比較容易理解。可以看到,將分頁顯示邏輯實現用單獨的實體表示來實現有點意外。不過PageBean配合前端的html代碼數據分頁顯示,做得十分精彩。

再看FindProductByIdServlet中調用了ProductServicefindProductById()方法直接獲取該商品的詳細信息,普通用戶正常購物流程來說直接跳轉info.jsp方便下單,但是源碼還有個管理員跳轉至編輯商品的流程,不過在列表展示時,並沒有設置type的邏輯。先不管。

// 得到商品的id
		String id = request.getParameter("id");
		// 獲取type參數值,此處的type用於區別普通用戶和超級用戶
		String type = request.getParameter("type");		
		ProductService service = new ProductService();		
		try {
			// 調用service層方法,通過id查找商品
			Product p = service.findProductById(id);
			request.setAttribute("p", p);
			// 普通用戶默認不傳遞type值,會跳轉到info.jsp頁面
			if (type == null) {
				request.getRequestDispatcher("/client/info.jsp").forward(request,response);
				return;
			}						
			request.getRequestDispatcher("/admin/products/edit.jsp").forward(request, response);
			return;
		} catch (FindProductByIdException e) {
			e.printStackTrace();
		}

info.jsp中,確認該商品時會調用AddCartServlet

<a href="${pageContext.request.contextPath}/addCart?id=${p.id}">
	<img src="${pageContext.request.contextPath }/client/images/buybutton.gif" border="0" width="100" height="25" /> 
</a>

AddCartServlet中,完成了創建、管理購物車對象cart的邏輯,實質上它是一個Map鍵值對對象,用商品productkey用數量countvalue,並且以"cart"爲索引存儲在Session中,一旦退出登錄或者其存活時間到了,購物車cart便會消失。把比較有趣的是,Map集合的key是唯一的,如果使用put()方法存儲,當key重複時,put()方法返回原來的value值。源碼巧妙利用了這個方法,實現了
區別商品是否已經添加過了,是的話除了直接put()還要數量+1,對於新商品來講數量就沒必要+1了。

AddCartServlet

// 1.得到商品id
		String id = request.getParameter("id");
		// 2.調用service層方法,根據id查找商品
		ProductService service = new ProductService();
		try {
			Product p = service.findProductById(id);
			//3.將商品添加到購物車
			//3.1獲得session對象
			HttpSession session = request.getSession();
			//3.2從session中獲取購物車對象
			
			@SuppressWarnings("unchecked")
			Map<Product, Integer> cart = (Map<Product, Integer>)session.getAttribute("cart");
			//3.3如果購物車爲null,說明沒有商品存儲在購物車中,創建出購物車
			if (cart == null) {
				cart = new HashMap<Product, Integer>();
			}
			//3.4向購物車中添加商品
			Integer count = cart.put(p, 1);
			//3.5如果商品數量不爲空,則商品數量+1,否則添加新的商品信息
			if (count != null) {
				cart.put(p, count + 1);
			}			
			session.setAttribute("cart", cart);
			response.sendRedirect(request.getContextPath() + "/client/cart.jsp");
			return;
		} catch (FindProductByIdException e) {
			e.printStackTrace();
		}

然後就到cart.jsp中,頁面效果如圖:
在這裏插入圖片描述
主要特點還是集中在紅框部分,分成幾部分:書本數量實時修改、繼續購物回到全部分類顯示、結賬跳轉賬單頁面。

自定義jsp標籤:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="p" uri="http://www.itcast.cn/tag"%>

翻看jsp代碼,文件頭有兩個標籤引用,除了標準的c標籤還自定義了一個p標籤,指向的是PrivilegeTag這個類,同時在WEB-INF根目錄下還要指定userPrivilegeTag.tld相應的配置文件,不然映射不到http://www.itcast.cn/tag內容。
userPrivilegeTag.tld本質上是xml文檔:

<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.1" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	                    http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd">
	<!--指定版本信息-->
	<tlib-version>1.0</tlib-version>
	<!--引用的名字-->
	<short-name>p</short-name>
	<!--指定映射uri-->
	<uri>http://www.itcast.cn/tag</uri>
	<tag>
		<!--指定屬性名稱-->
		<name>user</name>
		<!--指定實際路徑-->
		<tag-class>cn.itcast.itcaststore.tag.PrivilegeTag</tag-class>
		<body-content>empty</body-content>
	</tag>
</taglib>

不難看出自定義的p標籤,通過uri和名字找到指定類和配置文件,可以獲取一個叫user屬性,至於PrivilegeTag裏面做了什麼?獲取上下文的RequestResponse,還有一個登陸後一直保存着的User對象,如果不存在(未登錄就訪問購物車頁面),就會跳轉權限不足的錯誤頁面,修改書本數量的操作就不能進行。對於自定義標籤怎麼用這裏不做詳細的探討,我們明白有這麼一個監測非法操作的用法。

public class PrivilegeTag extends SimpleTagSupport {
	@Override
	public void doTag() throws JspException, IOException {
		PageContext context = (PageContext) this.getJspContext();
		HttpServletRequest request = (HttpServletRequest) context.getRequest();
		HttpServletResponse response = (HttpServletResponse) context.getResponse();
		
		User user = (User) context.getSession().getAttribute("user");
		if (user == null) {
			response.sendRedirect(request.getContextPath() + "/client/error/privilege.jsp");
		}
	}
}

爲什麼要使用標籤?
程序員定義的一種JSP標籤,這種標籤把那些信息顯示邏輯封裝在一個單獨的Java類中,通過一個XML文件來描述它的使用。當頁面中需要使用類似的顯示邏輯時,就可以在頁面中插入這個標籤,從而完成相應的功能。使用自定義標籤,可以分離程序邏輯和表示邏輯,將Java代碼從HTML中剝離,便於美工維護頁面;自定義標籤也提供了可重用的功能組件,能夠提高工程的開發效率。自定義標籤主要用於移除Jsp頁面中的java代碼。JSP引擎將遇到自定義標籤時,首先創建標籤處理器類的實例對象,然後按照JSP規範定義的通信規則依次調用它的方法。

往下看cart.jsp:

<script>
    //當商品數量發生變化時觸發該方法
	function changeProductNum(count, totalCount, id) {
		count = parseInt(count);
		totalCount = parseInt(totalCount);
		//如果數量爲0,判斷是否要刪除商品
		if (count == 0) {
			var flag = window.confirm("確認刪除商品嗎?");

			if (!flag) {
				count = 1;
			}
		}
		if (count > totalCount) {
			alert("已達到商品最大購買量");
			count = totalCount;
		}
		location.href = "${pageContext.request.contextPath}/changeCart?id="
				+ id + "&count=" + count;
	}
	//刪除購物車中的商品
	function cart_del() {   
	    var msg = "您確定要刪除該商品嗎?";   
	    if (confirm(msg)==true){   
	    return true;   
	    }else{   
	    return false;   
	    }   
	}   
</script>

腳本函數changeProductNum()location.href是本頁面跳轉訪問修改書本數量的ChangeCartServlet (即修改一下都要實時通知) 。
cart.jsp靈魂部分:

<!-- 循環輸出商品信息 -->
<c:set var="total" value="0" />
<c:forEach items="${cart}" var="entry" varStatus="vs">
	<table width="100%" border="0" cellspacing="0">
	<tr>
		<td width="10%">${vs.count}</td>
		<td width="30%">${entry.key.name }</td>
		<td width="10%">${entry.key.price }</td>
		<td width="20%">
		<!-- 減少商品數量 -->
		<input type="button" value='-' style="width:20px"
		onclick="changeProductNum('${entry.value-1}','${entry.key.pnum}','${entry.key.id}')">
		<!-- 商品數量顯示 -->
		<input name="text" type="text" value="${entry.value}" style="width:40px;text-align:center" />
		<!-- 增加商品數量 -->
		<input type="button" value='+' style="width:20px"														onclick="changeProductNum('${entry.value+1}','${entry.key.pnum}','${entry.key.id}')">
		</td>
		<td width="10%">${entry.key.pnum}</td>
		<td width="10%">${entry.key.price*entry.value}</td>
		<td width="10%">
		<!-- 刪除商品 -->
		<a href="${pageContext.request.contextPath}/changeCart?id=${entry.key.id}&count=0"
		style="color:#FF0000; font-weight:bold" onclick="javascript:return cart_del()">X</a>
		</td>
	</tr>
		</table>
		<c:set value="${total+entry.key.price*entry.value}" var="total" />
		/c:forEach>

total這個變量代表總價,遍歷數據的時候累加出實際總價,<c:set>標籤用於設置變量值和對象屬性。varStatus="vs"用於設置購物車物品前面的排列序號。在<c:foreach>語句中varStatus封裝了每一個子項的自動附加信息:count(整數型)、index(整數型)、first(布爾型)、last(布爾型)。每次點擊±按鈕都會調用changeProductNum(),傳入的分別是購買數量、庫存數量、書本ID。該方法觸發ChangeCartServlet決定修改後顯示的內容,之後又回到cart.jsp顯示:

// 1.得到商品id
		String id = request.getParameter("id");
		// 2.得到要修改的數量
		int count = Integer.parseInt(request.getParameter("count"));
		// 3.從session中獲取購物車.
		HttpSession session = request.getSession();
		Map<Product, Integer> cart = (Map<Product, Integer>) session.getAttribute("cart");
		Product p = new Product();
		p.setId(id);
		if (count != 0) {
			cart.put(p, count);
		} else {
			cart.remove(p);
		}
		response.sendRedirect(request.getContextPath() + "/client/cart.jsp");

順着流程圖來到order.jsp:
在這裏插入圖片描述
該jsp也是有自定義p標籤進行安全檢查,之後點擊提交訂單會提交到跳轉到CreateOrderServlet中,封裝訂單信息提交到數據庫:

// 1.得到當前用戶
		HttpSession session = request.getSession();
		User user = (User) session.getAttribute("user");
		// 2.從購物車中獲取商品信息
		Map<Product, Integer> cart = (Map<Product, Integer>)session.getAttribute("cart");
		// 3.將數據封裝到訂單對象中
		Order order = new Order();
		try {
			BeanUtils.populate(order, request.getParameterMap());
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		order.setId(IdUtils.getUUID());// 封裝訂單id
		order.setUser(user);// 封裝用戶信息到訂單.
		for (Product p : cart.keySet()) {
			OrderItem item = new OrderItem();
			item.setOrder(order);
			item.setBuynum(cart.get(p));
			item.setP(p);
			order.getOrderItems().add(item);
		}
		System.out.println(order);
		// 4.調用service中添加訂單操作.
		OrderService service = new OrderService();
		service.addOrder(order);
//		request.getRequestDispatcher("/client/orderlist.jsp").forward(request, response);
		response.sendRedirect(request.getContextPath() + "/client/createOrderSuccess.jsp");

循環封裝訂單子項的Product對象時,用到了MapkeySet()方法,這個方法返回一個關於Key的集合,因爲本身具有迭代器,可以在for語句中自增反覆賦值,取出所有的鍵。對於Dao的底層封裝就不細細探討了,去到createOrderSuccess.jsp中:
在這裏插入圖片描述

<script type="text/javascript" src="js/my.js">	</script>

這裏面有個倒計時的用法,時間一到或者直接點擊回到首頁的邏輯實現是由封裝的my.js腳本文件完成:

var interval;
window.onload = function() {
	interval = window.setInterval("changeSecond()", 1000);
};
function changeSecond() {
	var second = document.getElementById("second"); //獲取頁面元素
	var svalue = second.innerHTML;
	svalue = svalue - 1;
	if (svalue == 0) {
		window.clearInterval(interval);
		// 下列兩行代碼用於獲取項目名,例如:bookstore
		var pathName = window.location.pathname.substring(1);   
		var webName = pathName == '' ? '' : pathName.substring(0, pathName.indexOf('/'));
		// 拼接訪問路徑名,例如:http://localhost:8080/bookstore/index.jsp
		location.href = window.location.protocol + '//' + window.location.host + '/'+ webName + '/index.jsp'; 
		return;
	}
	second.innerHTML = svalue;
}

innerHTML 屬性設置或返回表格行的開始和結束標籤之間的 HTML代碼,這裏作用是改變倒計時的數字。該方法1000毫秒執行一次。setInterval() 方法可按照指定的週期(以毫秒計)來調用函數或計算表達式。該方法會不停地調用函數,直到 clearInterval() 被調用或窗口被關閉。由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的參數。當循環執行直到秒數爲0時,清除該ID指向的的循環操作。

總結一下,該部分算是該項目的核心部分,特別是分頁現實的功能實現特別NB,購買書本直到支付的邏輯順序也是很嚴格,可以想象一個完整的項目在前期的交互設計上面付出的心思也是必須面面俱到,嚴謹不能出錯。剛剛接觸純jsp+servlet的Java web項目,對於項目結構能有一個具體的認識收穫挺大的,很多東西沒耐心細細按部就班地學,看源碼找疑點分開學,確實能夠進步很快。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章