JavaWeb網上商城

web項目

1.關於web項目目的:
	將web階段所有學過的知識點複習總結.
	
2.關於web項目功能:
		功能: 
		1、用戶註冊
		2、用戶登錄
		3、添加商品(上傳)
		4、商品查看-- 列表查詢
		5、商品詳情頁面
		6、將商品添加購物車
		7、查看購物車
		8、修改購物車
		9、生成訂單
		10、訂單查看(取消)
		11、在線支付
		12、銷售榜單查看
		
		會對項目進行重構.
		會使用註解+動態代理實現細粒度權限控制.
		添加關於ajax操作
			對於訂單操作時,使用ajax
		在線支付功能	

3.系統分析
	1.通過UML用例圖來確定當前用戶以及所具有的功能
		遊客(未登錄): 註冊、登陸、商品查看 
		商城註冊用戶 : 商品查看、添加商品到購物車、購物車管理、生成訂單、訂單管理、在線支付 
		管理員 : 添加商品、商品管理、查看訂單 、榜單查看(導出)

	2.系統設計
		1.技術選型
			JSTL + JSP + Servlet + JavaBean + BeanUtils + FileUpload + JavaMail + DBUtils(JDBC) + C3P0 +  MySQL + MyEclipse10+ Tomcat7.0 + JDK6  + Windows 
			MVC 模式
			JavaEE 三層結構
			DAO 模式 
			
		2.數據庫設計
			E-R圖  實體關係圖.			
			對於我們當前項目有這些實體  用戶  商品  購物車  訂單 
			
			通過E-R圖可以分析出我們數據庫中表與表之間的關係,以及每一個表中的屬性。
			
			create table users (
			   id int primary key auto_increment,
			   username varchar(40),
			   password varchar(100),
			   nickname varchar(40),
			   email varchar(100),
			   role varchar(100) ,
			   state int , 
			   activecode varchar(100),
			   updatetime timestamp  );
			商品表
			create table products(
			   id varchar(100) primary key , 
			   name varchar(40), 
			   price double,
			   category varchar(40),
			   pnum int ,
			   imgurl varchar(100),
			   description varchar(255));
			訂單表
			create table orders(
			   id varchar(100) primary key,
			   money double,
			   receiverinfo varchar(255),
			   paystate int, 
			   ordertime timestamp,
			   user_id int , 
			   foreign key(user_id) references users(id)
			);

			用戶與訂單之間存在 一對多關係 : 在多方添加一方主鍵作爲外鍵 
			訂單和商品之間存在 多對多關係 : 創建第三張關係表,引入兩張表主鍵作爲外鍵 (聯合主鍵)
			訂單項
			create table orderitem(
			   order_id varchar(100),  
			   product_id varchar(100),
			   buynum int , 
			   primary key(order_id,product_id), 
			   foreign key(order_id) references orders(id), 
			   foreign key(product_id) references products(id)
			);

			設置數據庫環境
			數據庫 :create database estoresystem;
			
--------------------------------------------------------------------		
4.環境搭建	
	1.導入jar包
		導入mysql驅動  mysql driver / mysql-connector-java-5.0.8-bin.jar 
		導入c3p0  c3p0/c3p0-0.9.1.2.jar  將c3p0-config.xml 複製src下  將DataSourceUtils複製 cn.itcast.estore.utils  ----- 配置c3p0-config.xml數據庫連接參數
		導入dbutils apache commons\dbutils\commons-dbutils-1.4.jar
		導入beanutils commons-beanutils-1.8.3.jar commons-logging-1.1.1.jar 
		導入fileupload commons-fileupload-1.2.1.jar commons-io-1.4.jar
		導入javamail mail.jar
		導入jstl jstl.jar standard.jar
	2.創建包結構
		cn.itcast.estore.web.servlet
		cn.itcast.estore.web.filter
		cn.itcast.estore.web.listener
		cn.itcast.estore.service
		cn.itcast.estore.dao
		cn.itcast.estore.domain
		cn.itcast.estore.utils

	3.創建domain
		UML中類圖畫法 
		
	4.工程發佈
		將estore項目配置虛擬主機,以頂級域名方式進行發佈 
		在瀏覽器上直接輸入www.estore.com就可以訪問到我們的工程.
		
		1.在tomcat的conf目錄下的server.xml文件中配置
			1.修改tomcat的端口 80.
			2.配置虛擬主機
				<Engine name="Catalina" defaultHost="www.estore.com">
				<Host name="www.estore.com"  appBase="D:\java1110\workspace\estore" unpackWARs="true" autoDeploy="true">
					<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
						   prefix="localhost_access_log." suffix=".txt"
						   pattern="%h %l %u %t "%r" %s %b" />
						   
					<Context path="" docBase="D:\java1110\workspace\estore\WebRoot"/>

				  </Host>
				  
			3.在C:\Windows\System32\drivers\etc\hosts文件中配置
				127.0.0.1  www.estore.com
			
			注意:在啓動tomcat時,不需要將工程estore工程部署到tomcat.
			
=========================================================================================
功能實現:
	1.註冊操作
		1.一次性驗證碼
		2.表單的js校驗(非空校驗)
		  服務器端的校驗	
		3.全局編碼過濾器
		4.密碼md5加密
		5.發送激活郵件
		6.通用錯誤頁面配置
		
	2.實現註冊功能
		1.修改靜態頁面
			直接創建一個jsp,將page.html頁面內容複製到home.jsp頁面.
			在index.jsp中添加一個請求轉發操作
			<jsp:forward page="/home.jsp"/>
			
		2.在home.jsp頁面上添加一個註冊的連接。
			<a href="${pageContext.request.contextPath}/regist.jsp">註冊</a>	
			
		3.創建一個regist.jsp頁面
			頁面上有
				1.username
				2.password
				3.email
				4.nickname
				
				還需要一個repassword 確認密碼
				還需要一個驗證碼
				
		4.在頁面上產生驗證碼
			1.將new_words.txt複製到WEB-INF目錄下.
			2.在regist.jsp頁面上
				<img src="${pageContext.request.contextPath}/checkImg" οnclick="change();" id="cimg">
			注意編碼問題.
				
		5.完成註冊的流程
			regist.jsp---->RegistServlet-----UserService   UserDao
			
			關於用戶的role與state
				對於註冊的用戶它的role我們默認設置爲"user",state默認設置爲0 代表沒有激活,
				對於激活碼,我們在註冊時,要生成。通過uuid生成.
				
			註冊成功後跳轉到 regist_success.jsp頁面,在頁面上顯示3秒後跳轉到首頁.
				
			問題:
				1.怎樣讓3秒數字變化.
					通過js代碼來完成
					var interval;
					window.onload = function() {
						interval = window.setInterval("fun()", 1000); //設置1秒調用一次fun函數
					};

					function fun() {
						var time = document.getElementById("s").innerHTML;

						//判斷如果等於0了,不在進行調用fun函數,
						if (time == 0) {
							window.clearInterval(interval);
							return;
						}

						document.getElementById("s").innerHTML = (time - 1);
					}
				2.關於亂碼處理
					使用全局的編碼過濾器
					EncodingFilter完成post與get請求的編碼處理.
					
		6.關於註冊操作的校驗問題
			1.表單校驗
				就是通過js代碼完成.
				1.在<form οnsubmit="return checkForm();">
				2.校驗,只校驗非空.
					function checkNull(fieldName){
						var value = document.getElementById(fieldName).value;

						var reg = /^\s*$/; //代表0個或多個空字符。		

						if(reg.test(value)){
							document.getElementById(fieldName+"_message").innerHTML="<font color='red'>"+fieldName+"不能爲空</font>";
							return false;
						}else{
							return true;
						}
					}
			2.服務器端校驗
				在javaBean中做一個校驗方法
					public Map<String, String> validation() {
						Map<String, String> map = new HashMap<String, String>();
						if (username == null || username.trim().length() == 0) {
							map.put("regist.username.error", "用戶名不能爲空");
						}
						if (password == null || password.trim().length() == 0) {
							map.put("regist.password.error", "密碼不能爲空");
						}
						return map;
					}
					
				在通過BeanUtils將請求參數封裝到javaBean後,調用校驗方法.
				如果判斷Map集合中有數據,說明存儲了錯誤信息,就跳轉到regist.jsp頁面。
				在頁面上展示錯誤信息,使用jstl標籤。
				
				
				
		7.關於發送激活郵件
			郵箱 [email protected]
			密碼  abc123
			
			關於激活郵件發送我們在UserService中的regist方法中完成。
			
			關於MailUtils工具使用:
					1.props.setProperty("mail.host", "自己郵箱的smtp");
					2.return new PasswordAuthentication("郵箱帳戶", "密碼");
					3.message.setFrom(new InternetAddress("發送者郵箱"));
			
				注意:關於發送的信息問題:
				String emailMsg="註冊成功,
					請<a href='http://www.estore.com/activeUser?activeCode="+user.getActivecode()+"'>激活</a>,
				激活碼爲:"+user.getActivecode();
		
			
		8.關於激活用戶操作					
			http://www.estore.com/activeUser?activeCode=d104ed31-0a5f-4eb4-8377-085c9be5f6e4	

			1.創建一個ActiveUserServlet	
				1.得到激活碼,
				2.調用service中激活操作
					注意:激活是有時間限制的。
				3.在service中完成操作時有兩件事要做:
					1.根據激活碼查找用戶
					2.判斷用戶激活碼沒有過期,進行激活操作.
					
		9.md5加密
			
			mysql中:UPDATE users SET PASSWORD=MD5(PASSWORD);
			
			在UserDao的addUser方法中,對user.getPassword()使用Md5Utils工具進行加密。
			
		10.驗證碼
			
			在所有操作前,通過requst獲取請求中的驗證三,與session中存儲的驗證碼進行對比。
			從session中獲取完成後,馬上刪除。
			
			在CheckImgServlet中有一句話:
					request.getSession().setAttribute("checkcode_session", word);
					
					String checkCode = request.getParameter("checkcode");

					String _checkCode = (String) request.getSession().getAttribute(
							"checkcode_session");
					request.getSession().removeAttribute("checkcode_session");//從session中刪除。

					if (!checkCode.equals(_checkCode)) {
						request.setAttribute("regist.message", "驗證碼不正確");
						request.getRequestDispatcher("/regist.jsp").forward(request,
								response);
						return;
					}
================================================================================================================
2.登錄操作
	1.登錄操作中具有的功能
		
		1.請求信息的校驗
			1.客戶端校驗
			2.服務器端校驗
		2.請住用戶名操作
		3.自動登錄操作
		4.註銷功能

		登錄注意事項:
			1.注意用戶是否激活
			2.注意密碼已經進行了md5加密。			
			用戶登錄成功後,將用戶存儲到session中.
			
	2.登錄代碼實現:
		1.登錄基本流程
			1.在home.jsp頁面上有登錄窗口。
			2.創建一個LoginServlet
				完成登錄操作 ,在LoginServlet中
					1.得到用戶名與密碼
					2.調用service完成登錄操作.
					
			3.在service中判斷用戶是否可以登錄,以及用戶是否激活	
			4.在home.jsp頁面上,完成錯誤信息展示以及用戶登錄的提示。
	------------------------------------------------------------
		2.記住用戶名操作
			原理:當用戶登錄成功後,會判斷用戶是否勾選了記住用戶名,如果勾選了,將用戶名存儲到cookie中。
			    下一次在訪問登錄頁面,直接從cookie中獲取用戶名,顯示在用戶名文本框上。
				
			問題:cookie中不能存中文?
				
				存:Cookie cookie = new Cookie("remember", URLEncoder.encode(user.getUsername(), "utf-8"));
				取:
					window.οnlοad=function(){//頁面加載成功後跳用這個函數。
						var username=document.getElementById("username");
						window.de
						username.value=window.decodeURIComponent("${cookie.remember.value}","utf-8");
					};
				關於刪除cookie:
					1.setMaxAge(0/-1) 0代表立即刪除 -1代表關閉瀏覽器後才刪除。 
					2.刪除cookie時,必須與原cookie的path值一致.
	-----------------------------------------------------------------------
		3.自動登錄
			原理:用戶登錄成功,判斷是否勾選了自動登錄,如果勾選了,將用戶名與密碼存儲到cookie中。
				需要一個Filter,當用戶訪問工程時,在Filter中從cookie取出用戶名與密碼,進行登錄操作。
				
				注意事項:
					1.存密碼時,注意加密。
					2.如果用戶已經登錄,不需要在登錄。
					3.如果訪問的資源路徑不需要自動登錄,那麼不進行自動登錄。
					4.第一個用戶自動登錄,又使用了第二個用戶登錄,它沒有勾選自動登錄,
					  這時,需要將自動登錄的cookie刪除。
					  
	-------------------------------------------------------------------------
		4.註銷	
			在home.jsp頁面有註解的連接。
				<a href="${pageContext.request.contextPath}/logout">註銷</a>
			創建一個LogOutServlet完成註解功能
				session.invalidate();
				
			問題:銷燬session的方式:
				
				1.關閉服務器
				2.invalidate()				
				3.自動超時
					在tomcat/conf/web.xml文件中配置超時時間
					 <session-config>
						<session-timeout>30</session-timeout>
					</session-config>
				4.setMaxInactiveInterval(int interval) 
					手動設置session超時時間.
					
			注意:如果我們有自動登錄操作,那麼當我們完成註銷操作後,會跳轉到首頁,
                這時,如果在cookie中存儲了用戶名與密碼,就會進行自動登錄,那麼
				註銷的效果就看不到了,所以要看到效果,可以將自動登錄的cookie刪除。
				
		問題:
			1.一個用戶在兩個瀏覽器登錄
				要想解決,需要將數據存儲到數據庫中。
				可以使用session共享服務器來解決.
			2.兩個用戶在同一個瀏覽器登錄
				會出現共享session問題,簡單說,第一個用戶購買的物品,存儲在session中。
				第二個用戶登錄後,直接就可以看到第一個用戶購買的商品。				
				解決方案:在每一個用戶登錄時,先銷燬session.
============================================================================================================
商品操作:
		1.添加商品(上傳操作)
			1.在home.jsp頁面上有一個連接
				<a href="${pageContext.request.contextPath}/addProduct.jsp">添加商品</a>
				
			2.創建addProduct.jsp頁面
				問題:頁面上有什麼組件?
					查看products表中的數據.
				文件上傳時瀏覽器端注意:
					1.method=post
					2.encType="multipart/form-data"
					3.<input type="file" name="f">
				
			3.根據表中的數據創建Product類
				private String id; // 商品編號
				private String name; // 名稱
				private double price; // 價格
				private String category; // 分類
				private int pnum; // 數量
				private String imgurl; // 圖片路徑
				private String description; // 描述
				
			4.編寫AddProductServlet
				完成添加商品操作----其實是文件上傳.				
				1.DiskFileItemFactory
				2.ServletFileUpload
				3.FileItem
				
				在這個servlet中要完成兩件事情:
					1.文件上傳
						問題:
							1.上傳文件中文名亂碼
								upload.setHeaderEncoding("utf-8");
							2.上傳文件名稱獲取?
								item.getName(); 得到的有可能包含路徑。
								
							3.上傳文件名稱重複
								uuid獲取隨機名稱 
								
							4.上傳文件隨機目錄。
								通過文件名的hashCode進行計算,隨機得到目錄.
								
							5.關於上傳文件保存位置
								對於我們這個項目,上傳的商品圖片,是允許瀏覽器直接訪問的,
								所以我們保存到WebRoot下的upload目錄下.
						
					2.向products表中添加數據。
						1.得到所有數據封裝到Product對象中.
							BeanUtils.populate(product,Map);
							這個Map怎樣得到?
								手動創建一個Map<String,String[]> 將數據手動封裝.
								
							問題:關於id怎樣封裝?
								uuid獲取.
							問題:關於imgurl怎樣封裝?	
									map.put("imgurl", new String[]{"/upload"+uuidDir+"/"+uuidname})
						2.調用service,dao完成添加操作.
						
						3.添加成功後,跳轉到首面.
					
					
				
		---------------------------------------------------------------	
		2.查看商品
			1.查看全部
				index.jsp---->findAllProduct------->home.jsp
				1.創建一個FindAllProductServlet
					在這個servlet中查詢出所有商品List<Product>,將其存儲到request域,在請求轉發到home.jsp頁面
					
				2.在home.jsp頁面展示
					
			
				<div class="art-content-layout overview-table">
					<div class="art-content-layout-row">
					<c:forEach items="${ps}" var="p" varStatus="vs">
						<div class="art-layout-cell">
							<div class="overview-table-inner">
								<h4>${p.name }</h4>
								<img src="${pageContext.request.contextPath}${p.imgurl}" width="55px" height="55px"
									alt="an image" class="image" />
								<p>價格: ¥${p.price }</p>
								<p>速速搶購</p>
							</div>
						</div>										
						<c:if test="${vs.count%5==0}">
							</div> <!-- 判斷當前已經有5個商品了,這 一行結束,在重新開啓一行 -->
							<div class="art-content-layout-row">
						</c:if>		
					</c:forEach>
						<!-- end cell -->
					</div>
					
					<!-- end row -->
				</div>
				<!-- end table -->
			--------------------------------------------------	
			2.查看商品詳細信息		
				它有兩個入口,一個是點擊速速搶購.還有點擊圖片也可以查看商品詳細信息.
					1.<a href="${pageContext.request.contextPath}/findProductById?id=${p.id}">速速搶購</a>
					2.在圖片上添加一個onclick
						function findProductById(id){
							location.href="http://www.estore.com/findProductById?id="+id;
						};
					查看商品詳細信息:
						1.創建FindProductByIdServlet
							1.得到商品id
							2.根據id調用service,dao完成查詢商品操作.
							
						2.創建productInfo.jsp頁面,展示商品信息
						
						關於展示商品時,
							<img>展示商品圖片,可以通過它的width與height屬性來控制圖片的大小。
							
						在開發中還有另外一種處理方式:使用商品圖片的縮略圖。
							我們可以自己編程,去獲取一個上傳圖片的縮略圖。
							在展示時,直接得到它的縮略圖來展示 .
							
						在文件上傳完成後,添加這兩名話就會產生圖片的縮略圖
							// 生成縮略圖
							PicUtils putils = new PicUtils(dest.getCanonicalPath());// 獲取上傳文件的絕對磁盤路徑。

							putils.resize(200, 200);// 就會產生一個200*200的縮略圖.
						使用縮略圖
							1.在Product類中添加一個方法
								public String getImgurl_s() { 
									int index = imgurl.lastIndexOf(".");

									return imgurl.substring(0, index) + "_s" + imgurl.substring(index);
								}
							2.在productInfo.jsp頁面上
								<img src="${pageContext.request.contextPath}${p.imgurl_s}">
==========================================================================================================
購物車
	我們使用的session來存儲購物車,在數據庫中沒有關於購物車中商品信息。
	1.添加商品到購物車
		在productInfo.jsp頁面有連接。
		問題:怎樣將商品添加到購物車,購物車我們使用什麼數據結構來存儲商品信息?
			
			購物車我們使用Map<Product,Integer>
			
			在productInfo.jsp頁面上連接,它要傳遞商品的id
			function addProductToCart(id){		
				location.href="${pageContext.request.contextPath}/addProductToCart?id="+id;
			}
			
		創建一個AddProductToCartServlet	
			1.根據id得到商品
			2.得到購物車
			3.將商品添加到購物車
			
			注意:我們使用的購物車其實是一個HashMap<Product,Integer>,對於HashMap,它的維護主鍵唯一性,是使用
			    key值的hashCode與equals方法,也就是說,對於我們的購物車,它是使用Product的hashCode與equals方法
				來保證,我們同一個商品的數量的變化.
				
				簡單說,對於我們就需要重寫Product類的equals方法與hashCode方法。
				
	--------------------------------------------------------------------------------------
	2.查看購物車商品
		有兩個入口
			1.添加商品到購物車成功後,有提示,查看購物車
			2.在首面有查看購物車
			
		查看購物車,我們直接就是一個jsp頁面上將購物車中商品展示出來就可以。
		因爲購物車就存儲在session中.
		
		創建一個showCart.jsp,用於展示購物車中所有商品.
		
		
		購物車中商品總價怎樣獲取:每個商品的單價*商品數量
			<c:set var="totalMoney" value="${totalMoney+c.key.price*c.value}"/>
		
	-----------------------------------------------------------------------------------------
	3.改變購物車中商品數量
		1.加操作
			點擊加按鈕,要訪問一個servlet,在servlet中,獲取購物車中商品,對其數量進行操作.
			
			function changeCount(id,count){
		
				location.href="${pageContext.request.contextPath}/changeCount?id="+id+"&count="+count;
			}
			在服務器端:
			Product p=new Product();
			p.setId(id);			
			cart.put(p, count);
			
		問題:怎樣控制邊界?
			如果數量減到0,相當於將商品從購物車中刪除。
			如果數量加到比商品庫存還大,就讓它等於最大值.
			在js代碼中控制
			//控制邊界
				if (count <= 0) {
					//刪除
					var flag = window.confirm("要刪除商品嗎?");
					if (flag) {
						count = 0;
					} else {
						count = 1;

					}
				} else if (count >= pnum) {
					alert("最大購物數量"+pnum);
					count = pnum;
				}
			注意:如果購物數量爲0,這時會以服務器端將商品從購物車中刪除。
			
		關於文本框中數量修改:
			對於文本框,可以添加一個onblur事件
			注意:在js中數據是無類型的,操作時,有的進修傳參數,想要傳遞的是一個數值類型,
			但是,js將其做爲字符串處理了,就需要使用parseInt() parseFloat()來轉換成數值類型。
			
			
		數字文本框:
			在<input type="text">這個文本框中只能輸入數字,不能輸入其它的字符。
			原理:給文本框添加onkeydown事件,當觸發事件時,獲取按下的鍵的鍵碼值,如果它的鍵碼是數字0-9之間的就可以執行,
			     如果不是,阻止事件的默認行爲 .
				 1.問題:怎樣獲取按下的鍵碼值
				 2.問題:怎樣阻止事件的默認行爲
				 
				function a(e){
					var keyCode;
					if(e&&e.preventDefault){
						//判斷是firefox瀏覽器
						keyCode=e.which;
					}else{
						//ie瀏覽器
						keyCode=window.event.keyCode;
					}
					//alert(keyCode);
					//0-9之間的鍵碼值是48-57
					if(!(keyCode>=48&&keyCode<=57||keyCode==8)){
						//阻止事件的默認行爲
						if(e&&e.preventDefault){
							// e對象存在,preventDefault方法存在 ---- 火狐瀏覽器
							e.preventDefault();
						}else{
							// 不支持e對象,或者沒有preventDefault方法 ---- IE
							window.event.returnValue = false;
						}
					}
				}; 
	---------------------------------------------------------------------------
	從購物車中刪除商品
		1.連接提交時,將商品id攜帶到服務器
			<a href="${pageContext.request.contextPath}/removeProductFromCart?id=${c.key.id}">刪除</a>
		2.創建RemoveProductFromCartServlet	將要刪除的商品從購物車中刪除
			// 得到要刪除的商品的id
			String id = request.getParameter("id");
			// 得到購物車,從購物車中將商品刪除,
			Map<Product, Integer> cart = (Map<Product, Integer>) request
					.getSession().getAttribute("cart");
			Product p = new Product();
			p.setId(id);
			cart.remove(p);

			//如果購物車中無商品,將購物車刪除。
			if (cart.size() == 0) {
				request.getSession().removeAttribute("cart");
			}
			
		關於刪除商品時的提示處理:
			方式1:
				<a href="javascript:void(0)" οnclick="removeProduct('${c.key.id}')">刪除</a>
				function removeProduct(id) {
					var flag = window.confirm("要刪除商品碼?");
					
					if(flag){
						//要刪除
						location.href="${pageContext.request.contextPath}/removeProductFromCart?id="+id;
					}
				}
				
			方式2:通過阻止事件的默認行爲來控制
				<a href="${pageContext.request.contextPath}/removeProductFromCart?id=${c.key.id}"
						οnclick="deleteProduct(event)">刪除</a>
				function deleteProduct(e) {
					var flag = window.confirm("要刪除商品碼?");
					if (!flag) {
						//不刪除,阻止連接的默認行爲 執行。
						//阻止事件的默認行爲
						if (e && e.preventDefault) {
							// e對象存在,preventDefault方法存在 ---- 火狐瀏覽器
							e.preventDefault();
						} else {
							// 不支持e對象,或者沒有preventDefault方法 ---- IE
							window.event.returnValue = false;
						}
					}
				};

============================================================================================================
訂單操作
	1.生成訂單
		1.在showCart.jsp頁面上,有一個連接,進行結算中心,
			應該跳轉到一個order.jsp頁面.在這個頁面上輸入訂單的相關信息。
			輸入完成後,提交信息,生成訂單。
			
		2.表單提交,訪問一個AddOrderServlet,完成生成訂單操作.
			1.將數據封裝到Order對象中.
				Order order=new Order();
				//它封裝了訂單的  送貨地址,總價.
				BeanUtils.populate(order, request.getParameterMap());
				String id=UUID.randomUUID().toString();
				order.setId(id);//封裝訂單的id
				order.setPaystate(0);//默認值爲0,代表未支付。如果爲1,代表支付.
				
				//封裝user_id
				//從session中獲取當前用戶.
				User user=(User) request.getSession().getAttribute("user");
				int user_id=user.getId();
				order.setUser_id(user_id);
			問題:我們生成訂單,會對幾張表操作?
				
				1.insert into orders
				2.insert into orderItem
				3.update products set pnum=pnum-?;
				
			對於訂單操作,必須添加事務處理.
			
		3.對DataSourceUtils進行修改
				// 獲取綁定到ThreadLocal中的Connection。
				public static Connection getConnectionByTransaction() throws SQLException {
					Connection con = tl.get();
					if (con == null) {
						con = dataSource.getConnection();
						tl.set(con);
					}

					return con;
				}

				// 開啓事務
				public static void startTransaction(Connection con) throws SQLException {
					if (con != null)
						con.setAutoCommit(false);
				}

				// 事務回滾
				public static void rollback(Connection con) throws SQLException {
					if (con != null)
						con.rollback();
				}

				public static void closeConnection(Connection con) throws SQLException {
					if (con != null) {
						con.commit();// 事務提交
						con.close();
						tl.remove();

					}
				}
			注意:在dao中在使用QueryRunner時,不要使用有參數構造,要使用無參數構造,
				使用QueryRunner的batch,update方法時,要帶Connection參數,而Connection對象的獲取是
				通過getConnectionByTransaction來獲取的。
				
	------------------------------------------------------------------------------	
	查看訂單:
		
		1.查看訂單時,如果當前用戶是admin角色,可以查看所有人訂單,如果用戶是user,只能查看自己訂單。
		
		2.查看訂單實現:
			1.select * from orders;----->List<Order>
			2.查詢訂單中商品的信息。
			
		3.代碼實現:
			1.點擊查看訂單時,訪問一個ShowOrderServlet,查詢出所有訂單信息
				select * from orders;----->List<Order>
				將List集合存儲到request域,跳轉到showOrder.jsp頁面在,展示所有信息.
				
				response.getWriter().write("訂單生成成功,<a href='"+request.getContextPath()+"/showOrder'>查看訂單</a>");
				<a href="${pageContext.request.contextPath}/showOrder"></a>
				
			2.在showOrder.jsp頁面展示訂單:
				
			
			問題:展示訂單時,想要顯示當前訂單是哪個用戶的,怎樣得到用戶名?
				
				我們需要將orders與users表關聯查詢.
				String sql = "select users.username,users.nickname,orders.* from orders,users where users.id=orders.user_id ";
				
				查詢出的數據要封裝到Order對象中,但是不能封裝username,nickname,所以我們在Order類中添加了兩個屬性
				private String username;
				private String nickname;
				
			-------------------------------------
			3.查看訂單中商品詳細信息 ajax完成
			
				1.查找某一個訂單中所有商品信息的sql語句
					SELECT 
						數據 
					FROM 
						orderitem,products 
					WHERE 
						orderitem.product_id=products.id 
					AND 
						orderitem.order_id="訂單id";
				
				2.使用ajax完成操作
					1.得到XMLHttpRequest對象
					2.onreadstatechange 註冊回調函數
					3.open
					4.send
					5.在回調函數中操作.
					
					我們查詢出訂單中商品信息,以json格式返回.
					
	---------------------------------------------------------------------------------------------
	刪除訂單
		1.在showOrder.jsp頁面有刪除訂單的連接。點擊連接時,將訂單的id傳遞到服務器端,完成根據id刪除訂單操作.
		
			問題:如果訂單是已經支付的怎樣處理?如果是沒有支付怎樣處理?
				我們人爲規定,如果是已支付訂單,不能刪除。如果是未支付訂單可以刪除。
				<c:if test="${order.paystate==0}">
					<a href="#">刪除</a>
				</c:if>				
				<c:if test="${order.paystate!=0}">
					刪除
				</c:if>
			問題:對於未支付訂單,刪除時,怎樣操作?
				
				需要做三件事情:
					1.delete from orders where id=?;
					2.delte from orderitem where order_id=?
					3.update products set pnum=pnum+? where id=?;
					
				在操作時,需要先刪除orderitem表中數據,在刪除orders中數據。					
				我們最後要修改products表中的數據,而它需要的buynum,與product_id都是在orderitem表中存在的。
				
				分析完成上面操作,我們在代碼實現時,步驟:
					1.select * from orderitem where order_id=?;----->List<OrderItem>
					2.delete from orderitem where order_id=?;
					3.delete from order where id=?
					4.updat products set pnum=pnum+buynum where id=product_id;
					
			我們操作時,需要對多表進行操作,也需要事務控制.
		2.代碼實現:
			1.在showOrder.jsp頁面添加連接路徑.
				<a href="${pageContext.request.contextPath}/delOrder?orderid=${order.id}">刪除</a>
				
			2.創建DelOrderServlet
				1.得到要刪除的訂單id.
				2.調用service完成刪除訂單操作.
				
			3.在OrderService中創建一個方法 delOrderById(String orderid);
				在這個方法中.
				1.開啓事務
				2.根據orderid查詢出所有的orderitem中數據.得到一個List<Orderitem>
				3.根據orderid在orderitem中刪除數據
				4.根據orderid在orders事刪除數據
				5.根據List<OrderItem>在products表中修改數據.
				6.如果有異常,事務回滾,沒問題,事務提交,資源釋放。
					
	--------------------------------------------------------------------------------
	訂單支付:
		在線支付---新的知識點
		
		1.在showOrder.jsp頁面,如果當訂單狀態是爲支付,我們將其設置成一個連接。
			<a href='${pageContext.request.contextPath}/pay.jsp?orderid=${order.id}&money=${order.money}'>未支付</a>
		2.創建一個pay.jsp頁面,它就是我們的支付頁面。
			在頁面上得到訂單號,金額信息,並且可以選擇支付的銀行.
			
		3.完成在線支付
			
			1.什麼是在線支付?在線支付實現方式?
				通過網絡直接完成訂單的支付。
				
				有兩種方式:
					1.直接與銀行做對接
						優點:不會有延遲。
						缺點:開發,維護費用比較高.銀行接口變動,需要更改。
						這種方式不適合中小商戶。
					2.使用第三方支付
						優點:方便,不用處理是怎樣支付
						缺點:有延遲,會收取一定費用。
						這種方式比較適合中小商戶。
						
			2.我們使用的是第三方支付
				
				現在使用的比較多第三方支付  支付寶  財富通  快錢  
				我們使用 易寶支付(http://www.yeepay.com/)
				
				在線支付條件:
					1.可以上網。
					2.需要開通網銀.
					
				開發在線支付條件:
					1.可以上網.
					2.需要一個獨立ip.  
					3.需要在易寶支付申請一個商家賬號。  10001126856
					
				在線支付流程:
					查看圖
					
			--------------------------------------------
			在線支付代碼實現:
				1.pay.jsp,頁面上有訂單號,金額以及選擇的銀行。
				
				2.OnLinePayServlet,這個servlet就完成數據的收集,以及向第三方支付發送數據過程.
					
					問題:
						1.要發送給第三方支付的信息有哪些?		
							
						2.發送給第三方支付的路徑是什麼?	
							https://www.yeepay.com/app-merchant-proxy/node
							

						以上這兩個問題,我們需要查詢易寶支付開發手冊。
						
						重要屬性:p8_Url 是易寶支付反饋信息時的路徑。
						
						hmac=數據+密鑰+算法.
						
						密鑰:L69cl522AV6q613Ii4W6u8K6XuW8vM1N6bFgyv769220IuYe9u37N4y7rI4Pl
						
			
				3.創建一個CallBackServlet,用於接收第三方支付返回的信息.
					
					http://www.estore.com/callBack?p1_MerId=10001126856&r0_Cmd=Buy&r1_Code=1&r2_TrxId=315223279200392I&r3_Amt=0.01&r4_Cur=RMB&r5_Pid=&r6_Order=asdflkasdiej&r7_Uid=&r8_MP=&r9_BType=1&ru_Trxtime=20141222105450&ro_BankOrderId=2636414683141222&rb_BankId=BOC-NET&rp_PayDate=20141222105443&rq_CardNo=&rq_SourceFee=0.0&rq_TargetFee=0.0&hmac=1852414bf1f5f63587a59e40cd8c35f2
						
					
					關於第三方返回信息重點:
						r9_BType  交易結果返回類型
						爲“1”: 瀏覽器重定向; 如果關閉瀏覽器,就可能接收不到信息。						
						爲“2”: 服務器點對點通訊.---要求必須返回一個success,否則會一直髮送。
						
		-----------------------------------------------------------------------------------------------
		在線支付完成後,修改訂單的狀態。
			update order set paystate=1 where id=r6_order;
			
=================================================================================================
	下載銷售榜單
		就是一個文件下載操作.
		
		問題:怎樣獲取到下載文件中的數據?
			要在已經支付的訂單中查找銷售的商品名稱以及數量。
			
			select 
				products.name,sum(buynum) totalSaleNum
			from	
				products,orderitem,orders
			where
				orderitem.product_id=products.id
			and
				orders.id=orderitem.order_id
			and
				orders.paystate=1
			group by
				pname
			order by
				totalSaleNum
				
		下載操作:
			1.下載榜單連接
				<a href="${pageContext.request.contextPath}/download">
			2.創建一個DownloadServlet
				1.得到數據
				2.根據數據,通過response.getWriter()流寫回到瀏覽器端.
						 
				
				
			3. 榜單文件是什麼格式?
			導出Excel 使用 POI類庫 

			csv 格式文件 , 逗號分隔文件 
			1) 信息當中有,在兩端加 雙引號 
			2) 信息當中有" 在之前加雙引號 轉義 

			文件下載 
			設置Content-Type、Content-Disposition 頭信息
			文件流輸出 (輸出文件內容)

			Excel 默認讀取字符集gbk
	
==============================================================================================	
權限
	1.url級別權限控制(粗粒度權限控制)
	
		原理:得到當前的訪問的資源路徑,得到當前用戶角色,來判斷當前用戶是否有權限訪問該資源.
			1.得到資源路徑.
				String uri=request.getRequestURI();
				String contextPath=request.getContextpath();
				String path=uri.substring(contextPath.length());
				
			2.當到當前用戶角色,通過角色,判斷當前用戶是否有權限訪問path資源。
				1.得到當前用戶
					request.getSession().getAttribute("user");
				2.做配置文件,在配置文件中聲明每個角色具有的權限。
					amdin.properties
					user.properties.
					
		實現:
			1.添加商品----->admin
			2.下載榜單----->admin			
			3.添加商品到購物車  購物車操作。-----user
			3.關於訂單操作     user  admin
			
			代碼實現:
				1.創建兩個配置文件
					user.properties  配置關於user角色具有的權限
					admin.properties 配置關於admin角色具有的權限.
					將配置文件放置在WEB-INF下.
				2.創建一個PrivilegeFilter進行權限控制
					1.在其init方法中將配置文件中內容讀取出來裝入到admins,users集合中。
					2.得到請求資源路徑,判斷是否需要權限.
				
				3.創建一個自定義異常,如果權限不足,拋出這個異常。
					在web.xml文件中配置全局異常處理.
					<error-page>
						<exception-type>cn.itcast.estore.exception.PrivilegeException</exception-type>
						<location>/error/privilege.jsp</location>
					</error-page>
=========================================================================================================================	
	

重構
	1.一個請求一個servlet,現在要做一個模塊一個servlet,也就是說,多個請求會訪問同一個servlet.
		UserServlet  註冊   登錄  註銷   激活
		CartServlet  關於購物車操作
		ProductServlet 關於商品操作   注意:我們重構時沒有將添加商品處理.
		OrderServlet   關於訂單操作
		
	2.對servlet中的操作在進行一次重構
		原因:
			在UserServlet中
				if ("regist".equals(method)) {
					regist(request, response);
				} else if ("login".equals(method)) {
					login(request, response);
				}
			在ProductServlet中
				if ("findProductById".equals(method)) {
					findProductById(request, response);
				} else {
					// 默認就是查詢所有.
					findAllProduct(request, response);
				}
		上面的代碼,操作是一致的,我們可以在一次進行抽取。生成一個BaseServlet.
		
			它的代碼
			String methodName = request.getParameter("method");
			Method method = this.getClass().getDeclaredMethod(methodName,
					HttpServletRequest.class, HttpServletResponse.class);
			method.invoke(this, request, response);
			
			舉例分析:			
			http://www.estore.com/user?method=login
			1.知道要訪問的是UserServlet。
			2.因爲UserServlet extends BaseServlet,這時就會訪問
			  BaseServlet中的service方法。
			3.在BaseServlet中
				request.getParameter("method");--->login
				
			4.得到指定的servlet中指定方法
				Method method = this.getClass().getDeclaredMethod(methodName,HttpServletRequest.class, HttpServletResponse.class);
				這句話就相當於得到了UserServlet中的login方法。
				
			5.method.invoke(this,request,response);
				這句話就相錄於
				UserServlet.login(request,response);
				
		-----------------------------
		以上重載操作後,
			在訪問servlet中的方法時,只需要   /url-pattern值?method=方法名。

2.細粒度(annotation+動態代理)
	
	原理:在service中的方法上添加一個註解,註解的值代表的是訪問這個功能所需要的權限名稱。
		我們的service的獲取,是通過一個工廠獲取的,而在工廠中返回的是service的動態代理對象。
		在動態代理中去控制是否有權限訪問當前操作。
		
		
	代碼實現:
		1.創建一個註解
				@Retention(RetentionPolicy.RUNTIME)
				@Target(ElementType.METHOD)
				@Inherited
				public @interface PrivilegeInfo {

					String value();
				}
		2.抽取service
			抽取出接口.例如:
				public interface ProductService {
				// 添加商品
				@PrivilegeInfo("添加商品")
				public void addProduct(Product p) throws Exception;

				// 查詢所有商品
				public List<Product> findAll() throws Exception;

				// 根據id查詢商品
				public Product findById(String id) throws Exception;

				// 下載榜單數據
				@PrivilegeInfo("下載榜單")
				public List<Product> downloadSell(User user) throws PrivilegeException,
						Exception
			}
		3.創建ServiceFactory
			在serviceFactory中通過動態代理生成一個service代理對象,返回這個代理對象.
			在servlet中使用的都是通過ServiceFactory獲取的service對象,也就動態代理對象。
			
			ProductService service = ProductServiceFactory.getInstance();
		
		4.在動態代理的InvocationHandler的invoke方法中處理。
			1.判斷方法上是否有註解,也就知道,是否需要權限控制.
				boolean flag = method
								.isAnnotationPresent(PrivilegeInfo.class);
			2.得到註解中的屬性值,也就得到了訪問該方法的權限名稱
				String pname = method.getAnnotation(
									PrivilegeInfo.class).value();
			3.得到當前用戶(對於需要權限控制的方法,在其方法的第一個參數,都設置爲User)
				User user = (User) args[0];
				
			4.得到user的role,在權限配置文件中查找這個角色所具有的權限名稱
				List<String> pnames = Arrays.asList(ResourceBundle
									.getBundle("privilege").getString(role)
									.split(","));
			5.判斷這個角色是否可以執行該方法.
					if (!pnames.contains(pname)) {
						throw new PrivilegeException();
					}
					
			問題:拋出異常不跳轉到指定的頁面?
				原因:我們操作是在invoke方法中執行的。而invoke方法拋出的是Throwable,我們自己拋出的PrivilegeException,
					會被包裝。					
					在外面捕獲不到。
					
					
					
			
		在開如,我們對頁面的權限控制可以使用url級別,而對具體的行爲執行,可能通過細粒度權限控制。
			
			
	
	
				
			
			
			
								
		

發佈了66 篇原創文章 · 獲贊 22 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章