基於Java Web的網上圖書商城管理系統——(三)
三、詳細設計
1.註冊
regist.jsp頁面------>UserServlet----->UserDao
UserServlet中:
1.獲取驗證碼,判斷它是否正確,如果正確,向下執行.
如果不正確,跳轉到regist.jsp頁面,顯示錯誤資訊
2.將所以請求引數封裝到User物件中.在User類中建立一個validateRegist,
這個方法會對請求引數進行校驗,將錯誤資訊封裝到一個Map集合,在Servlet中
判斷集合長度是否>0就可以判斷是否有錯誤資訊,如果有,跳轉到regist.jsp
顯示錯誤資訊.
3.呼叫UserService去完成註冊操作 呼叫regist方法,傳遞User引數
4.在regist方法中做了兩件事情
(1).呼叫UserDao完成註冊操作
(2).給註冊的使用者傳送了一封啟用郵件
關於md5加密:
在mysql資料庫中通過 md5(欄位);
update users set password=md5(password);
在java中可以通過程式碼實現
MessageDigest.getInstance("md5")
2.登入
index.jsp---->page.jsp頁面 有登入視窗
會有登入視窗,提交時會訪問UserServlet,會帶一個請求引數 method=login
在UserServlet中就可以判斷當前操作是登入操作
就會呼叫UserServlet中的login方法
UserServlet---UserService----->UserDao
1.userServlet中收集了使用者名稱與密碼
2.UserService中呼叫UserDao中查詢使用者操作 findUserByUserNameAndPassword
3.在UserService中判斷了一下得到的User物件是否為null,如果為null,直接丟擲一個自定義異常.
4.如果查詢到了使用者,但是使用者未啟用,那麼也不能登入成功,丟擲了一個自定義異常.
5.在UserServlet中捕獲自定義異常,在page.jsp頁面顯示錯誤資訊.
6.在UserServlet中判斷使用者不為空,就將User儲存到session中,並跳轉到首面index.jsp,
自動跳轉到page.jsp頁面.
1.記住使用者名稱
當用戶登入成功後,並且勾選了記住使用者名稱操作,我們將使用者的username儲存到cookie中,
持久化儲存,並攜帶到瀏覽器端.
在頁面上通過el表示式獲取username顯示出來.
在cookie中是否能儲存中文,那麼要是使用者名稱是中文,我們可以儲存username的utf-8碼.
在頁面上,通過js將utf-8碼解碼.
2.自動登入操作
當用戶登入成功後,並且勾選自動登入操作,我們將username,password都儲存到cookie中,
持久化儲存,並攜帶到瀏覽器端.
當下一次在訪問時,我們可以通過Filter來攔截我們請求,判斷cookie中是否有我們儲存username,
passowrd的這個cookie
注意:自動登入時,有以下情況是不需要進行自動登入的.
(1).使用者已經登入
(2).使用者訪問的路徑是 login regist這樣的操作。
3.登出操作
我們使用者登入成功後,會將使用者儲存到session中。
登出操作就是將session銷燬就可以以。
session.invalidate()方法.
點選登出訪問UserServlet?method=logout
3.商品新增
新增商品操作其時是一個檔案上傳操作.新增商品時,需要新增一個商品圖片,我們使用檔案上傳.
commons-fileupload
瀏覽器:
1.method=post
2.encType="multipart/form-data"
3.<input type="file" name="f">元件
點選新增圖書連線,會訪問 addProduct.jsp頁面
AddProductServlet這個servlet中有兩個工作:
1.完成書箱圖片的儲存(上傳操作)
2.將資訊儲存到資料庫.
建立了一個Map<String,String[]>它用於封裝所有請求引數
通過BeanUtils.populate方法將請求引數直接封裝到Product類中。
可以呼叫ProductService中的新增圖書的方法,完成圖書新增操作
當圖片新增成功後,我們會跳轉到index.jsp頁面.
4.商品查詢
(1).查詢全部
index.jsp頁面直接跳轉到ProductServlet中.
執行findAll操作,也就是查詢出全部資訊.
呼叫ProductService----ProductDao完成查詢操作,得到所有商品資訊List<Product>
轉發到page.jsp頁面,在page.jsp頁面展示所有商品資訊.
(2).根據id查詢
點選搶購書籍,會訪問ProductServlet,
product?method=findById&id=xxx
會將這本書籍的id也攜帶到伺服器端.
會呼叫ProductService的findByid方法,根據id查詢書籍,也就是得到一個Product物件.
查詢到商品後,跳轉到productInfo.jsp頁面,展示了商品資訊
上傳的所有圖片大小不確定的,怎樣保證顯示商品時,它的大小?
通過一個工具類可以保證當前的商品的圖片大小一致
PicUtils putils = new PicUtils(this.getServletContext()
.getRealPath(product.getImgurl()));
putils.resize(200, 200);
當新增商品時,會生成商品圖片的一個縮圖,以方便我們在頁面上顯示。
在Product類中提供了一個getImgurl_s方法 ,這個方法,會根據商品圖片的
路徑,獲取到縮圖片的路徑,我們就可以直接在頁面上通過el表達工,獲取
縮圖的路徑,顯示出這個縮圖.
5.檢視商品詳情
在page.jsp頁面會顯示所以商品資訊,它提供一個連線,可以點選檢視
當前書籍的詳細資訊。
<a href='${pageContext.request.contextPath}/product?method=findById&id=${p.id}'>速速搶購</a>
會訪問一個servlet(ProductServlet)並且傳遞了method=findById,id=xxxx.
在servlet中根據傳遞的method值,判斷要執行的是findById方法,也就根據
id查詢書籍資訊.會呼叫ProductService中查詢操作的方法,在ProductService中
呼叫ProductDao中查詢書籍方法,最後得到一個Product物件,也就是商品資訊封裝
物件.將查詢到的Product物件封裝到request域中,請求轉發到productInfo.jsp頁面,
在頁面上展示我們書籍詳細資訊.
6.新增到購物車
宣告:購物車,沒有資料庫,直接使用session儲存資訊.
當productInfo.jsp頁面,點選新增商品到購物車時,會將商品的id傳遞到伺服器端
在頁面上點選新增商品到購物車會呼叫一個js函式
function addProductToCart(id) {
location.href = "${pageContext.request.contextPath}/cart?method=add&id=" + id;
}
在伺服器端會有CartServlet,它就是用於處理我們購物車操作.
1.得到商品id request.getParameter("id");
2.根據id查找出商品 Product物件.
3.關於購物車的資料結構
Map<Product,Integer> 它就是我們的購物車,最終Map集合會儲存到session中.
4.第一次新增商品到購物車時,在伺服器端,根據就沒有購物車,也就是沒有map集合.
得到的是null值,就可以知道是第一次購物,就可以將購物車創建出來,並且,將
商品新增到Map集合中。
5.如果不是第一次購物,查詢後得到的map集合就不為null,就說明購物車中可能已經存
在了商品,就需要考慮一個事情,就需要考慮一個事情,購物車中存在了當間要購買的
商品。
Map集合特點:
key是唯一的,如果使用put方法儲存,那麼,當key重複時,put方法
返回的就是原來的value值。
可以根據put方法返回值,來判斷商品在購物車中是否存在,
如果存在了,也就是說,put方法返回值不為null,這時就可以將返回值
+1,在重新儲存到map集合中。
7.檢視購物車
當點選檢視購物車中商品時,會跳轉到一個jsp頁面,購物車是儲存在session中的,
那麼在jsp頁面上就可以直接得到session中的商品資訊.
<a href="${pageContext.request.contextPath}/showCart.jsp">
使用jstl的forEach遍歷Map集合.
<c:forEach items=${cart} var="entry">
${entry.key} ----對於cart中的key它就是一個Product物件
${entry.value}---對於caft中的value它是一個Integer物件,其實就是商品數量
</c:forEach>
通過<c:set> 來完成商品總價計算操作.
8.購物車管理
1.關於資料修改問題
(1)關於點選+ -按鈕完成商品數量修改操作
當點選按鈕時會呼叫函式changeCount(商品的id,商品修改數量,商品的庫存)
onclick="changeCount('${entry.key.id }','${entry.value-1}','${entry.key.pnum}')"
在js中它是沒資料型別的,那麼當傳遞引數時,在函式中,可能認為它是一個字串,
那麼就會引起問題。通過parseInt()函式將數值轉換成數字.
在函式中處理資料後,會將資料傳遞到伺服器端
location.href = "${pageContext.request.contextPath}/cart?method=update&id=" + id
+ "&count=" + count;
在CartServlet中通過判斷method=update完成操作.
1).得到要修改商品的id,在得到要修改的商品數量值count.
2).直接對購物車中的商品進行操作.
3).為什麼直接建立一個Product物件,將id值賦值給它,就可以
直接修改商品數量.
原因:對Product類中的equals方法進行了重寫,只比較商品的id.
在重寫equalse方法時,也將hashCode方法重寫了.
(2).關於+號操作
以後面的原理一樣.
onclick="changeCount('${entry.key.id}','${entry.value+1}','${entry.key.pnum }')"
區別:-號操作,是將商品的數量進行-1操作
+號操作,是將商品的數量進行+1操作
3.文字框失去焦點時,也呼叫
onblur="changeCount('${entry.key.id}',this.value,'${entry.key.pnum}')
注意:傳遞了this.value,它代表的是文字框中的值
4.數字文字框
通過對文字新增onkeydown事件操作,當鍵盤按下時,會呼叫一個函式.numbText(event)
在函式中通過判斷按下鍵的keyCode值,就是鍵碼值,來判斷當前是否按下的是指定的
按鈕。
注意:對於firefox或ie瀏覽器,它們獲取事件物件event有區別。
code = e.which; 判斷firefox瀏覽器 得到鍵碼值.
code = window.event.keyCode; 判斷是ie瀏覽器,得到鍵碼值.
if (!(code >= 48 && code <= 57 || code == 8 || code == 46))
這是判斷當前按下的不是0-9 delete backspace
這時就要阻止事件的預設行為.
e.preventDefault(); firefox阻止預設行為執行
window.event.returnValue = false; ie瀏覽器阻止預設行為執行.
5.關於刪除操作
<a href="${pageContext.request.contextPath}/cart?method=remove&id=${entry.key.id}" onclick="delConfirm(event)">刪除</a>
這個超連線會訪問伺服器的一個CartServlet,method=remove代表要執行的是刪除操作。
並且將商品的id傳遞到伺服器端.
得到id後,new Product()這個物件的id值就是傳遞過去的。
對於Product類來說,它已經重寫了equals方法,它比較的就是id。
在map集合中就可以直接根據我們建立的Product物件,將商品刪除。
最後會判斷當前購物車中是否有商品,如果沒有,直接將購物車刪除。
確認刪除操作?
function delConfirm(e){
var flag=window.confirm("確認刪除商品嗎");
if(!flag){
//不刪除商品
//要想不刪除商品,要阻止事件的預設行為執行.
if(e&&e.preventDefault){
// e物件存在,preventDefault方法存在 ---- 火狐瀏覽器
e.preventDefault();
}else{
// 不支援e物件,或者沒有preventDefault方法 ---- IE
window.event.returnValue = false;
}
}
}
我們阻止超連線的預設事件執行.這樣超連線就不會向href指定的路徑傳送請求。
9.生成訂單
1.showCart.jsp頁面,點選結算會生成訂單
2.會跳轉到order.jsp頁面,在頁面展示我們訂單中的資訊.
需要輸入一上訂單的收貨地址.
生成訂單的程式碼實現:
(1).在order.jsp頁面表單會向 ${pageContext.request.contextPath}/order提交.
表單中有一個隱藏域 <input type="hidden" name="method" value="add">
(2).在 OrderServlet中有一個add方法,它是訂單新增操作.
訂單的添加註意事項:
當訂單生成後,需要對以下的表進行操作.
1).訂單表中要插入資料
2).商品表中的商品數量要進行修改(修改商品的庫存)
3).訂單與使用者之間也存在關係,新增訂單時,也需要得到當前使用者的id.
以上操作需要進行事務控制。
1).獲取Connection時,要使用同一個,需要在DataSourceUtils中對獲取
Connection物件操作進行修改,將其放入到ThreadLocale中.
2).Dbutils
QueryRunner 直接使用帶引數的 引數型別是DataSource型別。
new QueryRunner(DataSource ds);這個操作,在呼叫update,query方法,
一般不會帶Connection引數,這樣,它就是一條sql一個事務。
而現在我們需要事務管理,所以我們在使用QueryRunner時,就會
不帶引數 new QueryRunner(),而使用帶Connection引數的update,query方法.
---------------------------------
要注意訂單要包含商品資訊,這時就需要從session中獲取購物車,將購物車中的資訊封裝
Order物件中.
現在我們需要事務控制,所以我們在service層進行了事務的開啟
在新增訂單項時,使用了批處理,因為訂單與商品之間存在多對多關係,那麼
我們的中間表orderItem,它就有可能有多條資料,所以我們使用了QueryRuner
的batch方法完成新增訂單項操作
注意:當我們操作完成後,一定要將Connection物件從ThreadLocale中remove掉.
10.檢視訂單
檢視訂單,會根據使用者的role去顯示出不同的訂單。
如果role=admin 它查詢出所有的訂單.
如果role=user 它只查詢出當前使用者的訂單.
程式碼實現:
檢視訂單的入口:
1).在首頁,提供了檢視訂單連線
2).當用戶新增完成訂單成功後,會顯示檢視訂單操作.
<a href="${pageContext.request.contextPath}/order?method=search">
(1).當點選連線後會訪問OrderServlet,並提交一個引數 method=search;
(2).在OrderServlet會首先得到當前使用者 request.getSession().getAttribute("user");
如果使用者沒有登入,會讓它登入,如果使用者登入了,會檢視訂單資訊.
(3).當呼叫dao中查詢訂單操作時,會根據當前使用者的role,進行不同的sql語句操作。
查詢出訂單後,訂單中不包含商品資訊,所以要根據訂單的資訊,在orderItem表與
products表中查詢出商品資訊。
(4).查詢出所有訂單後,會得到一個List<Order>,將集合儲存到request域中,最後請求轉發到
showOrder.jsp頁面,在頁面上顯示出所有查詢出的訂單.
11.訂單管理
1.支付操作
使用了線上支付操作 epay第三方支付平臺.
在顯示訂單頁面上showOrder.jsp頁面,顯示訂單資訊中,包含了當前支付狀態。
會顯示 "已支付" "未支付",如果是未支付,會有一個連線訪問pay.jsp頁面,
並將當前訂單的id,以及當前訂單的金額傳遞到pay.jsp頁面。
(1).在pay.jsp頁面上可以選擇銀行,表單提交時,將訂單編號,金額,以及銀行,提交到OnlinepayServlet中。
(2).在OnlinePayServlet中完成請求引數封裝.
(3).第三方支付,會根據你提交的請求引數 p8_Url 向這個路徑傳送資訊,
(4).可以指定p8_url為CallbackServlet,那麼我們在servlet中就可以得到支付結果資訊
(5).通過判斷資訊是否正確,以及r9_BType=1 r9_BType=2 可以知道,是否支付成功
(6).當判斷支付成功後,我們要修改訂單的狀態。
1).在orders表中有一個欄位 paystate=0 代表未支付,我們支付成功後,要修改訂單的狀態。
paystat=1 這個代表訂單已對付.
2).修改訂單狀態要根據訂單編號修改,在返回的支付結果資訊中r6_Order,它就代表了
我們的訂單編號。
---------------------------------------------
2.訂單取消
在顯示訂單的頁面上,會提供一個刪除訂單的連線。
(1).錄取消訂單時,這個超連線會攜帶當前訂單的編號傳遞到伺服器端.
<a href="${pageContext.request.contextPath}/order?method=del&id=${order.id}">取消訂單</a>
(2).這個連線會訪問OrderServlet,並且 method=del id=訂單編號
(3).OrderServlet中會根據傳遞method判斷執行取消訂單操作,會根據id知道要刪除哪一個訂單.
(4).刪除訂單注意事項
1).刪除訂單要將orders表中資料刪除---根據id刪除.
2).需要刪除orderItem表中資料
3).需要修改商品的數量 也就是說需要對products表進行update操作.
程式碼:
1).根據訂單id在orderitem表中查詢出相關的商品資訊.
2).修改商品資訊
3).刪除訂單項資訊
4).刪除訂單.
以上操作,也需要進行事務控制。
12.許可權控制
1.做一個註解
@Retention(RetentionPolicy.RUNTIME) //說明當前註解在runtime階段有效果
@Target(ElementType.METHOD) //當前註解是在方法上使用的
@Inherited //當前註解具有繼承性
public @interface PrivilegeInfo {
String value(); //許可權名稱
}
2.對所有的service層的類進行提取介面操作.
ProductServiceImpl 類---------->ProductService 介面
OrderServieImple類 ------------>OrderService介面.
在介面的方法上添加註解,註解中的value值,就是當前方法要執行,所需要的許可權名稱。
3.在servlet中得到的service物件,我們不直接new出來,可以針對每一個Service提供一個
工廠,在工廠中生產對應的service物件,並且,返回的是代理物件.
OrderServiceFactory
ProductServiceFactory
它們用於生產不同的service物件。
在工廠中創建出對應的service物件,在提供的getInstance()方法中,
返回其代理物件.
這樣我們在servlet中,就通過工廠獲取service物件。得到的其實是代理物件。
4.在動態代理的 InvocationHandler的invoke方法中進行許可權控制.
(1).得到當前方法上的註解
1).判斷當前方法上是否有指定的註解
2).如果沒有,代表這個操作不需要許可權控制.
如果有,就會存在我們的註解.
3).得到方法上的註解,通過註解物件得到當前方法要執行時所需要的許可權名稱。
4).得到註解後,還需要得到當前使用者,好麼我們在所有添加註解的方法上
添加了一個引數 User.
可以在invoke方法中通過args引數獲取User物件.
5).可以判斷當前使用者是否存在,知道是否有許可權操作。
1).如果使用者不存在,throw new PrivilegeException();許可權不足.
2).如果使用者存在
1).根據user的role,在資料庫中查詢出使用者所具有的所有的許可權名稱,
與註解上提供的許可權名稱對比。如果包含,那麼具有許可權,
如果不包含,沒有許可權
2).不包含 throw new PrivilegeException();許可權不足.
包含 method.invoke();
13.銷售榜單匯出
獲得商品銷售情況,需要查詢orderitem表 ------- 統計已支付訂單項內容
1.榜單中存在哪些資訊?(已支付訂單中商品)
商品資訊 products表
銷售數量 orderitem表
訂單支付情況 orders表
select * from products,orderitem,orders where products.id = orderitem.product_id and orderitem.order_id = orders.id ;
進行商品分組查詢 group by
select products.* , sum(orderitem.buynum) totalSaleNum from products,orderitem,orders where products.id = orderitem.product_id and orderitem.order_id = orders.id and orders.paystate = 1 group by products.id order by totalSaleNum desc;
2.榜單檔案是什麼格式?
匯出Excel 使用 POI類庫
csv 格式檔案 , 逗號分隔檔案
1) 資訊當中有,在兩端加 雙引號
2) 資訊當中有" 在之前加雙引號 轉義
檔案下載
設定Content-Type、Content-Disposition 頭資訊
檔案流輸出 (輸出檔案內容)
Excel 預設讀取字符集gbk