晚上心血來潮,為了復習之前學過的內容,溫故而至知新,所以決定寫一個簡易的網絡商城購物車,該小項目采用的技術非常簡單,基本上都是原生的,算是對基礎知識的復習。
一、項目實現
項目實現是基於Servlet的,數據庫采用MySql5.6,項目中用到了很多技術,下面通過代碼來說明。項目結構如圖所示:
該項目采用MVC模式,Controller負責接收請求並處理結果,服務層實現業務邏輯,dao層負責和數據庫進行交互,視圖層測采用了普通的html頁面顯示結果,為了提高安全性與效率為HttpRequest和HttpResponse添加了ThreadLocal變量,提高線程的安全性。關於Cookie,單獨寫了一個類CookieTools,該類提供了對cookie的增、刪、改操作的方法。單獨編寫了一個過濾器,目的是對中文編碼utf-8進行過濾,防止出現亂碼。另外,購物車的表格稍微加了一點樣式,這樣看起來不是那么的單調。
項目實現思想:該項目實現了兩個購物車,分別是公共購物車和私有購物車,公共購物車是在沒有登陸狀態下的所看到,任何人都可以操作,私有購物車是登陸以后才能看到的,公共購物車和私有購物車的商品可以合並,合並以后可以進行下單,下單后跳轉到支付頁面填寫支付收貨人信息等,收貨人信息實現了3級聯動。
下面詳細介紹項目的實現過程:
首先創建基於javaEE的項目shopping_cart,導入相關的jar包,這里主要用到的就是mysql-connector-java-5.1.7-bin.jar這個包,然后按照MVC模型的思想分別編寫各層的代碼。
1) 數據庫的設計
該項目涉及到的數據表格有:userinfo表,主要存放的是用戶的信息,只有數據庫中的用戶才能夠登陸。還有另外3張,分別為省表、市表和縣表,供填寫收貨人信息和實現三級聯動使用。表結構如圖所示:
2)封裝HttpRequest和HttpResponse
該類位於utils包下:將HttpRequest和HttpResponse放在ThreadLocal變量中,采用set方法放入,get方法獲取,代碼如下:
1 package utils; 2 import javax.servlet.http.HttpServletRequest; 3 import javax.servlet.http.HttpServletResponse; 4 5 public class RequestResponseBox { 6 7 private static ThreadLocal<HttpServletRequest> requestBox = new ThreadLocal<>(); 8 private static ThreadLocal<HttpServletResponse> responseBox = new ThreadLocal<>(); 9 10 public static void setRequest(HttpServletRequest request) { 11 requestBox.set(request); 12 } 13 14 public static void setResponse(HttpServletResponse response) { 15 responseBox.set(response); 16 } 17 18 public static HttpServletRequest getRequest() { 19 return requestBox.get(); 20 } 21 22 public static HttpServletResponse getResponse() { 23 return responseBox.get(); 24 } 25 26 }
3)封裝cookie
該類提供對cookie的增刪改的操作,具體代碼如下:
1 public class CookieTools { 2 3 /** 4 * 保存cookie 5 * @param cookieName 6 * @param cookieValue 7 * @param maxAge 8 * @return void 9 * @author wangsj 10 */ 11 public void save(String cookieName, String cookieValue, int maxAge) { 12 try { 13 cookieValue = java.net.URLEncoder.encode(cookieValue, "utf-8"); 14 Cookie cookie = new Cookie(cookieName, cookieValue); 15 cookie.setMaxAge(maxAge); 16 RequestResponseBox.getResponse().addCookie(cookie); 17 } catch (UnsupportedEncodingException e) { 18 e.printStackTrace(); 19 } 20 } 21 22 /** 23 * 獲取cookie值 24 * @param cookieName 25 * @return 26 * @return String 27 * @author wangsj 28 */ 29 public String getValue(String cookieName) { 30 String cookieValue = null; 31 try { 32 Cookie[] cookieArray = RequestResponseBox.getRequest().getCookies(); 33 if (cookieArray != null) { 34 for (int i = 0; i < cookieArray.length; i++) { 35 if (cookieArray[i].getName().equals(cookieName)) { 36 cookieValue = cookieArray[i].getValue(); 37 cookieValue = java.net.URLDecoder.decode(cookieValue, "utf-8"); 38 break; 39 } 40 } 41 } 42 } catch (UnsupportedEncodingException e) { 43 e.printStackTrace(); 44 } 45 return cookieValue; 46 } 47 48 /** 49 * 根據cookie名刪除cookie 50 * @param cookieName 51 * @return void 52 * @author wangsj 53 */ 54 public void delete(String cookieName) { 55 Cookie cookie = new Cookie(cookieName, ""); 56 cookie.setMaxAge(0); 57 RequestResponseBox.getResponse().addCookie(cookie); 58 } 59 60 }
3)封裝Filter
過濾器的主要作用是對傳入的request,response提前過濾掉一些信息,或者提前設置一些參數(本項目主要進行中文編碼),然后再傳入servlet是,防止中文出現亂碼。比較簡單,代碼如下:
RequestResponseFilter:
1 package filter; 2 3 import java.io.IOException; 4 5 import javax.servlet.Filter; 6 import javax.servlet.FilterChain; 7 import javax.servlet.FilterConfig; 8 import javax.servlet.ServletException; 9 import javax.servlet.ServletRequest; 10 import javax.servlet.ServletResponse; 11 import javax.servlet.http.HttpServletRequest; 12 import javax.servlet.http.HttpServletResponse; 13 14 import utils.RequestResponseBox; 15 16 public class RequestResponseFilter implements Filter { 17 18 @Override 19 public void init(FilterConfig filterConfig) throws ServletException { 20 } 21 22 @Override 23 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 24 throws IOException, ServletException { 25 RequestResponseBox.setRequest((HttpServletRequest) request); 26 RequestResponseBox.setResponse((HttpServletResponse) response); 27 chain.doFilter(request, response); 28 } 29 30 @Override 31 public void destroy() { 32 } 33 34 }
CharSetFilter:
1 package filter; 2 3 import java.io.IOException; 4 5 import javax.servlet.Filter; 6 import javax.servlet.FilterChain; 7 import javax.servlet.FilterConfig; 8 import javax.servlet.ServletException; 9 import javax.servlet.ServletRequest; 10 import javax.servlet.ServletResponse; 11 12 public class CharSetFilter implements Filter { 13 14 @Override 15 public void init(FilterConfig filterConfig) throws ServletException { 16 } 17 18 @Override 19 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 20 throws IOException, ServletException { 21 request.setCharacterEncoding("utf-8"); 22 response.setCharacterEncoding("utf-8"); 23 chain.doFilter(request, response); 24 } 25 26 @Override 27 public void destroy() { 28 } 29 30 }
4)封裝數據庫連接工具
該類主要是對數據庫進行連接,對該類進行封裝,方便dao層調用。代碼如下:
GetConnectionType類
1 package dbtools; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.SQLException; 6 7 public class GetConnectionType { 8 public static Connection getConnection() throws SQLException, ClassNotFoundException { 9 String url = "jdbc:mysql://localhost:3306/test"; 10 String driver = "com.mysql.jdbc.Driver"; 11 String username = "root"; 12 String password = "123456"; 13 Class.forName(driver); 14 Connection connection = DriverManager.getConnection(url, username, password); 15 return connection; 16 } 17 }
GetConnection類,該類依然使用了ThreadLocal變量,代碼如下:
1 package dbtools; 2 3 import java.sql.Connection; 4 import java.sql.SQLException; 5 6 public class GetConnection { 7 8 private static ThreadLocal<Connection> local = new ThreadLocal<>(); 9 //獲取鏈接 10 public static Connection getConnection() throws ClassNotFoundException, SQLException { 11 Connection conn = local.get(); 12 if (conn == null) { 13 conn = GetConnectionType.getConnection(); 14 conn.setAutoCommit(false); 15 local.set(conn); 16 } 17 return conn; 18 } 19 //提交事務 20 public static void commit() { 21 try { 22 if (local.get() != null) { 23 local.get().commit(); 24 local.get().close(); 25 local.set(null); 26 } 27 } catch (SQLException e) { 28 e.printStackTrace(); 29 } 30 } 31 //回滾 32 public static void rollback() { 33 try { 34 if (local.get() != null) { 35 local.get().rollback(); 36 local.get().close(); 37 local.set(null); 38 } 39 } catch (SQLException e) { 40 e.printStackTrace(); 41 } 42 } 43 }
5)實現第一個功能:登陸
登陸的基本思想:對輸入的用戶名和密碼進行校驗,然后到數據庫去匹配,如果匹配成功則允許等錄,否則,提示用戶有誤。該功能涉及到UserinfoDao,UserinfoService,Login這些類以及index.js里面編寫的login方法以及html頁面。代碼如下:
UserinfoDao:
1 package dao; 2 3 import java.sql.Connection; 4 import java.sql.PreparedStatement; 5 import java.sql.ResultSet; 6 import java.sql.SQLException; 7 8 import dbtools.GetConnection; 9 import entity.Userinfo; 10 11 public class UserinfoDao { 12 13 public Userinfo getUserinfo(String username, String password) throws SQLException, ClassNotFoundException { 14 Userinfo userinfo = null; 15 String sql = "select * from userinfo where username=? and password=?"; 16 Connection conn = GetConnection.getConnection(); 17 PreparedStatement ps = conn.prepareStatement(sql); 18 ps.setString(1, username); 19 ps.setString(2, password); 20 ResultSet rs = ps.executeQuery(); 21 while (rs.next()) { 22 int idDB = rs.getInt("id"); 23 String usernameDB = rs.getString("username"); 24 String passwordDB = rs.getString("password"); 25 userinfo = new Userinfo(); 26 userinfo.setId(idDB); 27 userinfo.setUsername(usernameDB); 28 userinfo.setPassword(passwordDB); 29 } 30 rs.close(); 31 ps.close(); 32 return userinfo; 33 } 34 35 public Userinfo getUserinfo(String username) throws SQLException, ClassNotFoundException { 36 Userinfo userinfo = null; 37 String sql = "select * from userinfo where username=?"; 38 Connection conn = GetConnection.getConnection(); 39 PreparedStatement ps = conn.prepareStatement(sql); 40 ps.setString(1, username); 41 ResultSet rs = ps.executeQuery(); 42 while (rs.next()) { 43 int idDB = rs.getInt("id"); 44 String usernameDB = rs.getString("username"); 45 String passwordDB = rs.getString("password"); 46 userinfo = new Userinfo(); 47 userinfo.setId(idDB); 48 userinfo.setUsername(usernameDB); 49 userinfo.setPassword(passwordDB); 50 } 51 rs.close(); 52 ps.close(); 53 return userinfo; 54 } 55 }
UserinfoService
1 package service; 2 3 import java.sql.SQLException; 4 5 import cookietools.CookieTools; 6 import dao.UserinfoDao; 7 import entity.Userinfo; 8 import f.F; 9 10 public class UserinfoService { 11 12 private UserinfoDao userinfoDao = new UserinfoDao(); 13 private CookieTools cookieTools = new CookieTools(); 14 15 public boolean login(String username, String password) throws SQLException, ClassNotFoundException { 16 Userinfo userinfo = userinfoDao.getUserinfo(username, password); 17 if (userinfo != null) { 18 cookieTools.save(F.CURRENT_LOGIN_USERNAME, username, 36000); 19 return true; 20 } else { 21 return false; 22 } 23 } 24 25 public Userinfo getUserinfoByUsername(String username) throws SQLException, ClassNotFoundException { 26 Userinfo userinfo = userinfoDao.getUserinfo(username); 27 return userinfo; 28 } 29 30 public String getCurrentLoginUsername() { 31 return cookieTools.getValue(F.CURRENT_LOGIN_USERNAME); 32 } 33 34 }
index.js
1 function login(){ 2 var usernameValue = $("#username").val(); 3 var passwordValue = $("#password").val(); 4 $.post("login?t=" + new Date().getTime(), { 5 "username": usernameValue, 6 "password": passwordValue 7 }, function(data){ 8 if (data == 'true') { 9 $("#loginForm").hide(); 10 initWelcomeUI(usernameValue); 11 } 12 else { 13 alert("登陸失敗請重新輸入!"); 14 } 15 }); 16 }
index.html
1 <div id="loginDIV"> 2 <div id="loginForm" style="display:none"> 3 username:<input type="text" id="username"> 4 <br/> 5 password:<input type="text" id="password"> 6 <br/> 7 <input type="button" value="登陸" onclick="javascript:login()"> 8 </div> 9 <div id="welcomeDIV" style="display:none"> 10 </div> 11 </div>
運行結果如下:比較單調,沒有加任何樣式
6)購物車的實現
購物車實現比較麻煩,本項目采用的是字符串拼接的方式實現的,比較原始,稍微復雜一點,加入購物車代碼在CartService類中,
商品信息采用的是:A商品id_數量-B商品id_數量的形式實現,部分代碼如下:
1 public void putCart(String bookIdParam) throws SQLException, ClassNotFoundException { 2 String cartName = ""; 3 String cartValue = ""; 4 String newCartValue = ""; 5 6 String currentLoginUsername = userinfoService.getCurrentLoginUsername(); 7 if (currentLoginUsername == null) { 8 cartName = F.PUBLIC_CART_NAME; 9 } else {// privataCart_userId 10 int userId = userinfoService.getUserinfoByUsername(currentLoginUsername).getId(); 11 cartName = F.PRIVATE_CART_NAME_PREFIX + userId; 12 } 13 cartValue = cookieTools.getValue(cartName); 14 if (cartValue == null) { 15 cartValue = bookIdParam + "_1"; 16 } else { 17 String[] bookinfoArray = cartValue.split("\\-"); 18 boolean isFindBookId = false; 19 for (int i = 0; i < bookinfoArray.length; i++) { 20 String bookId = bookinfoArray[i].split("\\_")[0]; 21 String bookNum = bookinfoArray[i].split("\\_")[1]; 22 if (bookId.equals(bookIdParam)) { 23 isFindBookId = true; 24 break; 25 } 26 } 27 if (isFindBookId == false) { 28 cartValue = cartValue + "-" + bookIdParam + "_1"; 29 } else { 30 for (int i = 0; i < bookinfoArray.length; i++) { 31 String bookId = bookinfoArray[i].split("\\_")[0]; 32 String bookNum = bookinfoArray[i].split("\\_")[1]; 33 if (bookId.equals(bookIdParam)) { 34 newCartValue = newCartValue + "-" + bookId + "_" + ((Integer.parseInt(bookNum)) + 1); 35 } else { 36 newCartValue = newCartValue + "-" + bookId + "_" + bookNum; 37 } 38 } 39 newCartValue = newCartValue.substring(1); 40 cartValue = newCartValue; 41 } 42 } 43 System.out.println(cartValue + " " + cartName); 44 cookieTools.save(cartName, cartValue, 36000); 45 }
商品信息的傳輸時采用以下的信息:
<list> <bookinfo> <id>1</id> <bookname>Java</bookname> <bookprice>33.56</bookprice> </bookinfo> <bookinfo> <id>2</id> <bookname>c++</bookname> <bookprice>56.45</bookprice> </bookinfo> <bookinfo> <id>3</id> <bookname>Hibernat</bookname> <bookprice>80.55</bookprice> </bookinfo> <bookinfo> <id>4</id> <bookname>Struts</bookname> <bookprice>53.45</bookprice> </bookinfo> <bookinfo> <id>5</id> <bookname>Java</bookname> <bookprice>34.22</bookprice> </bookinfo> <bookinfo> <id>6</id> <bookname>we</bookname> <bookprice>12.22</bookprice> </bookinfo> </list>
此外,書籍數量的更新采用ajax實現,實現頁面局部更新,從而避免頁面更新時的閃動,下面是部分的數量更新代碼:
1 function updateCartBookNum(operateType, cartType, bookId){ 2 $.post("updateCartBookNum?t=" + new Date().getTime(), { 3 "operateType": operateType, 4 "cartType": cartType, 5 "bookId": bookId 6 }, function(data){ 7 if (data == '1') { 8 updateCartBookNumTRInfo(cartType, bookId); 9 setCartBottomInfoTR(cartType); 10 } 11 else { 12 alert("更新書籍數量失敗!"); 13 } 14 }); 15 }
7) 購物車實現效果
輸入用戶名登陸后的頁面如下所示:
當點擊放入購物車按鈕是會有一個動畫效果,顯示2秒然后消失,當點擊顯示購物車按鈕時,會顯示私有購物車的一些信息,如下所示:
這里顯示的是雙購物車,即私有購物車和公共購物車,雙購物車中的商品可以進行合並,合並實現的思想是首先判斷商品id,如果id相同則進行數量的相加,否則進行字符串的拼接。代碼如下:
1 public void putPrivateCart(String publicCart_id_num_String) throws ClassNotFoundException, SQLException { 2 String cartName = ""; 3 String cartValue = ""; 4 String newCartValue = ""; 5 6 String currentLoginUsername = userinfoService.getCurrentLoginUsername(); 7 int userId = userinfoService.getUserinfoByUsername(currentLoginUsername).getId(); 8 cartName = F.PRIVATE_CART_NAME_PREFIX + userId; 9 cartValue = cookieTools.getValue(cartName); 10 11 if (cartValue != null) { 12 13 String[] publicBookinfoArray = publicCart_id_num_String.split("-"); 14 String[] privateBookinfoArray = cartValue.split("-"); 15 16 String differenceString = ""; 17 18 for (int i = 0; i < publicBookinfoArray.length; i++) { 19 String bookIdPublic = publicBookinfoArray[i].split("\\_")[0]; 20 String bookNumPublic = publicBookinfoArray[i].split("\\_")[1]; 21 boolean isFindBookId = false; 22 for (int j = 0; j < privateBookinfoArray.length; j++) { 23 String bookIdPrivate = privateBookinfoArray[j].split("\\_")[0]; 24 String bookNumPrivate = privateBookinfoArray[j].split("\\_")[1]; 25 if (bookIdPublic.equals(bookIdPrivate)) { 26 privateBookinfoArray[j] = bookIdPrivate + "_" 27 + ((Integer.parseInt(bookNumPublic)) + (Integer.parseInt(bookNumPrivate))); 28 isFindBookId = true; 29 } 30 } 31 if (isFindBookId == false) { 32 differenceString = differenceString + "-" + bookIdPublic + "_" + bookNumPublic; 33 } 34 } 35 for (int i = 0; i < privateBookinfoArray.length; i++) { 36 newCartValue = newCartValue + "-" + privateBookinfoArray[i]; 37 } 38 newCartValue = newCartValue + differenceString; 39 newCartValue = newCartValue.substring(1); 40 cartValue = newCartValue; 41 } else { 42 cartValue = publicCart_id_num_String; 43 } 44 System.out.println(cartValue); 45 cookieTools.save(cartName, cartValue, 36000); 46 }
點擊putPrivateCart按鈕,如果左側沒有商品被選中的時候,會彈出提示框,如圖所示:
選中,左側按鈕所對應的商品,便可以成功放入私有購物車,放入購物車就可以進行下單操作了,如圖點擊左下角的輸入訂單信息,便可以進入,訂單頁面,填好訂單,可以進行購物車的確認以及訂單的提交。
二、總結
到這里,一個簡易的購物車就基本實現了,但這僅僅只是宇哥雛形,改進的地方還有很多,鑒於篇幅和時間的關系,只貼出了部分代碼,其他源碼,感興趣的朋友可以下載附件研究改進,待改進的地方:
1、數據庫的設計有待完善,數據庫中的字段可以添加數量的實時更新,顯示庫存等。
2、商品添加購物車采用的字符串拼接比較麻煩,可以采用json格式的字符串和前台進行交互。
3、異常的捕捉不是很好,可以考慮多加入異常的處理。
4、沒有日志的記錄,可以采用log4j對日志進行處理,方便調試,明確問題出在哪里。
5、可以考慮引入Spring框架,進行充分解耦。
6、登錄功能的設計有待完善,后期可以加入注冊功能,登錄功能可以用SpringMVC或者Struts2框架進行改進,另外登陸驗證可以采用前后台雙向校驗的方式進行驗證。
以上這些均是該項目有待改進的地方,還有很多需要完善的地方,希望對大家的學習有所幫助。
源碼下載地址:http://files.cnblogs.com/files/10158wsj/shopping_cart.rar