本文講什么窃肠, 本文其實(shí)就是項目3的精華。
可以看到,購物車這樣一個功能模塊,在各種購物類APP或者web應(yīng)用中絕對是必不可少的東西.不論在大學(xué)中的課程設(shè)計,還是在實(shí)際的項目開發(fā)中,絕對非常重要且有些復(fù)雜的內(nèi)容.
在實(shí)際操作中,身邊有很多的小伙伴遇到編寫購物車的代碼的時候,有時候真的是一臉懵逼,總是搞不明白設(shè)計的思路,這就是本文寫作的原因.
所以,本文適合搞不清楚購物車實(shí)現(xiàn)原理,知道原理但是實(shí)際編碼不知道如何下手的小伙伴,我將給出一個思路以及實(shí)際的代碼供大家參考.
在本文中,我將會用盡可能簡單的句子,表達(dá)出我想表達(dá)的意思.廢話不多說,開始我們的購物車實(shí)戰(zhàn)!
購物車的幾種實(shí)現(xiàn)方式
購物車的實(shí)現(xiàn)方式有很多,但是最常見的就三種:Cookie,Session,數(shù)據(jù)庫.三種方法各有優(yōu)劣,適合的場景各不相同.
Cookie方法:通過把購物車中的商品數(shù)據(jù)寫入Cookie中,再通過瀏覽器進(jìn)行讀取.這個方法,適合在用戶沒有登錄的情況下使用,但是有個非常嚴(yán)重的缺點(diǎn),即在用戶禁用了Cookie的時候是無法使用的.
Session方法:通過Session來保存商品信息,這確實(shí)是個好的方法,適合用戶已經(jīng)登錄的情況,將數(shù)據(jù)放在Session中,用戶就能讀取購物車中的商品信息,而且速度十分的快.但是缺點(diǎn)也很明顯,由于Session是建立在用戶客戶端和服務(wù)器之間的,在Session中保存數(shù)據(jù),無疑會增加服務(wù)器的負(fù)擔(dān).
數(shù)據(jù)庫(Redis):數(shù)據(jù)庫無疑是一種非常棒的保存購物車中信息的有效途徑,且能夠持久化保存,但是問題也很明顯,那就是讀取速度會差強(qiáng)人意.
好了,下面來說一下幾種實(shí)現(xiàn)方式的應(yīng)用場景.
當(dāng)用戶沒有登錄的情況下,用戶將商品加入購物車,此時的商品信息是寫入了Cookie中,并且會設(shè)置一個保存時間,即使關(guān)閉瀏覽器過一段時間訪問仍能看到購物車中的信息.(或者將購物數(shù)據(jù)寫入Session中,但是關(guān)閉瀏覽器,購物車中的信息也就不見了)
用戶登陸后,如果在Session中存儲了商品信息且沒有關(guān)閉瀏覽器(如果在Cookie中存儲了商品信息且沒有過期),將會讀取其中的商品信息,并且將這些信息寫入數(shù)據(jù)庫中進(jìn)行持久保存.
本文的行文方式說明
經(jīng)過上面的講解,我想你一定對購物車有所了解,為了使讀者更加清晰的明白購物車的實(shí)現(xiàn),我們省去了在未結(jié)算的狀態(tài)下的持久化數(shù)據(jù)庫.
也就是說,在文章中,我將使用Session來實(shí)現(xiàn)購物車,并且當(dāng)用戶沒有登錄的情況下,禁止用戶將商品加入購物車.當(dāng)然你不必為此擔(dān)憂,即使我這樣做,我的代碼已經(jīng)包括了整個購物操作的絕大多數(shù)步驟.請耐心向下看.
此外,本文使用SSM框架作為行文代碼.
如果你是初學(xué)者也不必?fù)?dān)心,我將為你提供一套項目的源代碼,可以在我的GitHub中獲惹哉狻:餐廳點(diǎn)餐系統(tǒng),這套系統(tǒng)是基于servlet+jsp+mysql開發(fā)的,注釋非常完善,當(dāng)然最重要的模塊也就是下單模塊肯定是有的,而且非常完善,歡迎下載.
購物車模塊的實(shí)現(xiàn)
數(shù)據(jù)庫設(shè)計
用戶表
字段 意義
id 用戶id
userName 用戶名
password 用戶密碼
商品表
字段 意義
id 商品id
commName 商品名稱
price 商品價格
訂單表
字段 意義
id 訂單id
commName 商品名稱
count 商品數(shù)量
subtotal 商品小計
total 總價
用戶登錄
為了實(shí)現(xiàn)我上述的思路,肯定是要求用戶先行登錄.代碼如下.
LoginController
@Controller
public class LoginController {
? ? private LoginService loginService;
? ? private CommonService commonService;
? ? @Autowired
? ? public LoginController(LoginService loginService, CommonService commonService) {
? ? ? ? this.loginService = loginService;
? ? ? ? this.commonService = commonService;
? ? }
? ? @RequestMapping("/login")
? ? public String login(User user, HttpSession session, Model model) {
? ? ? ? //登錄驗(yàn)證
? ? ? ? if (loginService.isUser(user)) {
? ? ? ? ? ? List<Common> commons = commonService.selectAllCommons();
? ? ? ? ? ? model.addAttribute("commons", commons);
? ? ? ? ? ? model.addAttribute("username", user.getUsername());
? ? ? ? ? ? //把用戶信息保存到session中
? ? ? ? ? ? session.setAttribute("user", user);
? ? ? ? ? ? return "shopping";
? ? ? ? } else {
? ? ? ? ? ? model.addAttribute("message", "用戶名或密碼錯誤");
? ? ? ? ? ? return "index";
? ? ? ? }
? ? }
}
這是最常規(guī)的用戶登錄的代碼,思路就是拿著用戶名到數(shù)據(jù)庫里面查詢,如果能查到,則說明用戶名無誤,如果查不到則說明沒有此用戶,提示用戶注冊.如果用戶名存在,再比對密碼,一般密碼不會像我們這樣直接在數(shù)據(jù)庫里面使用明文密碼,都是會加鹽的(MD5算法).如果比對密碼的結(jié)果為true,則用戶可以登錄.比對過程isUser的代碼如下.
@Service
public class LoginService {
? ? private UserMapper userMapper;
? ? @Autowired
? ? public LoginService(UserMapper userMapper) {
? ? ? ? this.userMapper = userMapper;
? ? }
? ? /**
? ? * 判斷用戶名或密碼是否正確
? ? * @param user
? ? * @return
? ? */
? ? public boolean isUser(User user){
? ? ? ? UserExample example = new UserExample();
? ? ? ? UserExample.Criteria criteria? = example.createCriteria();
? ? ? ? criteria.andUsernameEqualTo(user.getUsername());
? ? ? ? List<User> eqUser = userMapper.selectByExample(example);
? ? ? ? //如果沒有查詢到user,則返回false
? ? ? ? if (eqUser == null){
? ? ? ? ? ? return false;
? ? ? ? }
? ? ? ? return eqUser.get(0).getPassword().equals(user.getPassword());
? ? }
}
確認(rèn)用戶登錄以后,需要把用戶信息放在session中以便后續(xù)過程使用.
###用戶購物模塊
當(dāng)用戶登錄以后,展示在用戶眼前的界面是這樣的(頁面模板來自菜鳥教程,經(jīng)過改編):
左欄中的數(shù)據(jù)來自登錄代碼中的
List<Common> commons = commonService.selectAllCommons();
model.addAttribute("commons", commons);
當(dāng)點(diǎn)擊加入購物車以后,觸發(fā)onlick事件:onclick="javascript:joinCart(${common.id})"
詳細(xì)代碼如下:
function joinCart(id) {
? ? ? ? $.ajax({
? ? ? ? ? ? url: "${pageContext.request.contextPath}/shop/joinCart",
? ? ? ? ? ? data: "id=" + id,
? ? ? ? ? ? type: "POST",
? ? ? ? ? ? success: function (result) {
? ? ? ? ? ? ? ? alert("加入購物車成功!");
? ? ? ? ? ? ? ? //清空購物車列表
? ? ? ? ? ? ? ? $("#shop_cart tbody").empty();
? ? ? ? ? ? ? ? //動態(tài)構(gòu)建購物車列表
? ? ? ? ? ? ? ? var obj = result;
? ? ? ? ? ? ? ? $.each(obj,function (index,item) {
? ? ? ? ? ? ? ? ? var emptyTd = $("<td></td>").append("#");
? ? ? ? ? ? ? ? ? var commnameTd = $("<td></td>").append(item.commname);
? ? ? ? ? ? ? ? ? var countTd = $("<td></td>").append(item.count);
? ? ? ? ? ? ? ? ? var subtotalTd = $("<td></td>").append(item.subtotal);
? ? ? ? ? ? ? ? ? $("<tr></tr>")
? ? ? ? ? ? ? ? ? ? ? .append(emptyTd)
? ? ? ? ? ? ? ? ? ? ? .append(commnameTd)
? ? ? ? ? ? ? ? ? ? ? .append(countTd)
? ? ? ? ? ? ? ? ? ? ? .append(subtotalTd)
? ? ? ? ? ? ? ? ? ? ? .appendTo("#shop_cart tbody");
? ? ? ? ? ? ? ? ? //設(shè)置總金額
? ? ? ? ? ? ? ? ? var totalSpan = document.getElementById("subtotal");
? ? ? ? ? ? ? ? ? totalSpan.innerHTML = item.total;
? ? ? ? ? ? ? ? });
? ? ? ? ? ? }
? ? ? ? })
? ? }
可以看到,當(dāng)觸發(fā)這個方法時,實(shí)際上是使用了異步請求的方式向服務(wù)端發(fā)送數(shù)據(jù),服務(wù)端做相應(yīng)處理以后,封裝購物車列表,然后把購物車商品列表以JSON格式傳回,也就是封裝在result中,利用js,動態(tài)構(gòu)建購物車列表.于是就出現(xiàn)下面這種情況.
當(dāng)將商品加入購物車以后:
首先提示用戶已經(jīng)加入購物車,然后在利用異步請求構(gòu)建整個購物車,如果你對前端的了解并不是很深,不必?fù)?dān)心,這部分內(nèi)容實(shí)際上很簡單,你可以隨便百度一下這個知識點(diǎn),記住就好了.實(shí)際上就是利用js操作json數(shù)據(jù)而已.
其實(shí)對于初學(xué)者來說,感覺上面的過程挺神奇的,其實(shí)不然.前端的代碼看完了,我就來給你詳細(xì)的解釋一下后端的代碼.
首先,可以將后段代碼分成兩部分,一部分是判斷,各種判斷,另外一部分是封裝數(shù)據(jù),相對簡單.
//標(biāo)識符:判斷是否存在此商品
boolean flag = false;
//通過id查詢商品信息
Common common = shopService.selectCommById(Integer.parseInt(id));
//從session中獲取購物車信息
List<Order> shopcart = (List<Order>) session.getAttribute("shopcart");
//獲取用戶信息
User user = (User) session.getAttribute("user");
if (user == null) {
? ? //如果用戶為空,則直接返回,讓用戶登錄
? ? throw new RuntimeException("用戶未登錄");
}
//判斷購物車列表是否為空
if (shopcart == null) {
? ? //new 一個集合
? ? shopcart = new ArrayList<>();
}
先判斷用戶是否已經(jīng)登錄,如果用戶已經(jīng)登錄,則執(zhí)行后續(xù)流程,如果用戶沒有登錄,則直接拋出異常,交給異步請求的error處理,提示用戶登錄即可.
如果用戶已經(jīng)登錄,則繼續(xù)下面的步驟,判斷購物車是否為空,為空也就是說明用戶沒有將任何商品加入購物車,則需要創(chuàng)建一個新的ArrayList集合,同時利用商品id查詢出的商品信息,封裝訂單對象.
else {
? ? for (Order order : shopcart) {
? ? ? ? //判斷是否存在此商品,存在則數(shù)量+1
? ? ? ? if (order.getCommname().equals(common.getCommname())) {
? ? ? ? ? ? flag = true;
? ? ? ? ? ? order.setCount(order.getCount() + 1);
? ? ? ? ? ? order.setSubtotal(order.getCount() * common.getPrice());
? ? ? ? }
? ? }
}
在遍歷的過程中,如果遍歷的數(shù)據(jù)的commName和查到的name相同的話,則證明是同一件商品,則將此商品的數(shù)量+1,然后再設(shè)置小計價格即可.同時將flag設(shè)置為true,表示遍歷到了同樣的數(shù)據(jù).
如果沒有遍歷到名稱相同的商品,則直接新建一個對象,封裝數(shù)據(jù),加入集合.
//如果購物車中沒有當(dāng)前商品的信息,則新增商品
if (!flag) {
? ? Order order = new Order();
? ? order.setCommname(common.getCommname());
? ? order.setCount(1);
? ? order.setSubtotal(common.getPrice());
? ? //把商品加入集合
? ? shopcart.add(order);
}
最后一步就是計算總價格,封裝數(shù)據(jù)即可.
//計算總價格
Double total = 0d;
for (Order order : shopcart) {
? ? total += order.getSubtotal();
}
for (Order order : shopcart) {
? ? order.setTotal(total);
}
//設(shè)置session
session.setAttribute("shopcart", shopcart);
//返回list
return shopcart;
購物車全部代碼如下:
Controller
@RequestMapping("/shop")
public class ShopController {
? ? private ShopService shopService;
? ? public ShopController(ShopService shopService) {
? ? ? ? this.shopService = shopService;
? ? }
? ? @RequestMapping("/joinCart")
? ? @ResponseBody
? ? public List<Order> joinCart(String id, HttpSession session, Model model) {
? ? ? ? //標(biāo)識符:判斷是否存在此商品
? ? ? ? boolean flag = false;
? ? ? ? //通過id查詢商品信息
? ? ? ? Common common = shopService.selectCommById(Integer.parseInt(id));
? ? ? ? //從session中獲取購物車信息
? ? ? ? List<Order> shopcart = (List<Order>) session.getAttribute("shopcart");
? ? ? ? //獲取用戶信息
? ? ? ? User user = (User) session.getAttribute("user");
? ? ? ? if (user == null) {
? ? ? ? ? ? //如果用戶為空,則直接返回,讓用戶登錄
? ? ? ? ? ? throw new RuntimeException("用戶未登錄");
? ? ? ? }
? ? ? ? //判斷購物車列表是否為空
? ? ? ? if (shopcart == null) {
? ? ? ? ? ? //new 一個集合
? ? ? ? ? ? shopcart = new ArrayList<>();
? ? ? ? } else {
? ? ? ? ? ? for (Order order : shopcart) {
? ? ? ? ? ? ? ? //判斷是否存在此商品,存在則數(shù)量+1
? ? ? ? ? ? ? ? if (order.getCommname().equals(common.getCommname())) {
? ? ? ? ? ? ? ? ? ? flag = true;
? ? ? ? ? ? ? ? ? ? order.setCount(order.getCount() + 1);
? ? ? ? ? ? ? ? ? ? order.setSubtotal(order.getCount() * common.getPrice());
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? //如果購物車中沒有當(dāng)前商品的信息,則新增商品
? ? ? ? if (!flag) {
? ? ? ? ? ? Order order = new Order();
? ? ? ? ? ? order.setCommname(common.getCommname());
? ? ? ? ? ? order.setCount(1);
? ? ? ? ? ? order.setSubtotal(common.getPrice());
? ? ? ? ? ? //把商品加入集合
? ? ? ? ? ? shopcart.add(order);
? ? ? ? }
? ? ? ? //計算總價格
? ? ? ? Double total = 0d;
? ? ? ? for (Order order : shopcart) {
? ? ? ? ? ? total += order.getSubtotal();
? ? ? ? }
? ? ? ? for (Order order : shopcart) {
? ? ? ? ? ? order.setTotal(total);
? ? ? ? }
? ? ? ? //設(shè)置session
? ? ? ? session.setAttribute("shopcart", shopcart);
? ? ? ? //返回list
? ? ? ? return shopcart;
? ? }
}
關(guān)于操作購物車商品數(shù)量及結(jié)算
首先說操作購物車商品數(shù)量,既然我們能夠按照通過id加商品的數(shù)量,肯定也是能夠按照商品id減商品的數(shù)量,這部分無需多說,相信按照上面的代碼,以你的聰明才智,肯定是能夠做出來的.
至于結(jié)算操作,就更加單了.
用戶點(diǎn)擊結(jié)算按鈕以后,跳轉(zhuǎn)到后臺,通過后臺代碼,先把session中保存的數(shù)據(jù)取出,然后遍歷將數(shù)據(jù)寫入數(shù)據(jù)庫,進(jìn)行持久化的操作即可.
購物車的源代碼:我發(fā)群里