電商專案day16(購物車實現)
今日目標:
理解購物車的思路
購物車登陸後儲存到redis中
一.需求分析
資料結構分析:
購物車與商家相關聯
我們由京東的案列,就是可以看出,不登入,也可以實現購物車的新增,不過天貓不可以,必須登陸後才能進行,新增到購物車
購物車列表中有多個購物車:[
京東自營(購物車物件){
商家id, sellerId
商家名稱:sellerName
購物車商品明細列表:
[
購物車明細物件(展示商品資訊){
榮耀9i
華為p20
}
]
}
疆界互聯旗艦店:[
和京東自營儲存資料結構相同
]
]
上面就是常用的購物車的儲存結構
二.實現思路以及解決方案
新增商品到購物車,登陸和未登陸都能實現新增購物車
未登陸時:
1.儲存到cookie中,這個方法有個弊端,就是隻能存4kb
2.localStorge 儲存能達到5kb 是基於html5儲存資料物件的
JSON.stringify();
JSON.parse();
3.快取到redis中作為key-value儲存
登陸後:
1.快取到redis中
登陸後:使用者名稱 作為redis儲存購物車的可以
登陸後將購物車列表中的商品提交生成訂單後,在清除購物車中的資料
三.未登陸時,購物車功能實現
首先搭建購物車的工程 cart_web cart_service cart_interface
工程搭建完畢,分析後端新增購物車的邏輯思路:
非常重要:
首先根據商品的id查詢 該商家是否存在於購物車中
1.商家對應的購物車不存在,購物車列表中
建立購物車物件,在存入購物車列表中
建立購物車物件時,指定該購物車的商家資訊,以及構建購物車明細物件和購物車明細列表
將購物車的明細物件新增到購物車明細列表中,將購物車明細列表新增到購物車物件,在將
購物車物件新增到購物車列表中
2.商家對應的購物車物件存在於購物車列表中
判斷該商品是否存在於該購物車商品的明細列表中
1.如果該商品不存在購物車明細列表中
則建立購物車明細物件,在新增到購物車明細裡列表中
2.如果該商品存在購物車明細列表中
修改購物車明細物件的數量和小計金額
首先我們先編寫未登陸時,的介面方法
/**
* 新增商品到購物車
*/
public List<Cart> addItemToCartList(List<Cart> cartList, Long itemId, Integer num);
/**
* 根據sessionId獲得購物車列表
* @param sessionId
* @return
*/
List<Cart> selectCartListFromRedis(String sessionId);
/**
* 儲存該sessionid 的 cartList 購物車列表
* @param sessionId
* @param cartList
*/
void saveCartListToRedis(String sessionId, List<Cart> cartList);
/**
* 合併新增前購物車的資料到登陸後的購物車列表
* @param cartList_sessionId
* @param cartList_username
* @return
*/
List<Cart> mergeCartList(List<Cart> cartList_sessionId, List<Cart> cartList_username);
/**
* 刪除之前未登陸的購物車的列表資料
* @param cartList_sessionId
*/
void deleteCartList(List<Cart> cartList_sessionId);
service實現的所有的功能,主要是:購物車的新增,以及通過sessionid獲取購物車列表,還有就是,未登陸時,購物車列表的資料,在登陸後會跟新到redis資料庫中,通過username的作為key值儲存,然後刪除登陸前購物車的資料
package com.pinyougou.cart.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.pinyougou.cart.service.CartService;
import com.pinyougou.groupentity.Cart;
import com.pinyougou.mapper.TbItemMapper;
import com.pinyougou.pojo.TbItem;
import com.pinyougou.pojo.TbOrderItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Service
@Transactional
public class CartServiceImpl implements CartService {
@Autowired
private TbItemMapper itemMapper;
@Autowired
private RedisTemplate redisTemplate;
/**
* 新增商品到購物車
* @param cartList
* @param itemId
* @param num
* @return
* 首先根據商品的id查詢 該商家是否存在於購物車中
1.商家對應的購物車不存在,購物車列表中
建立購物車物件,在存入購物車列表中
建立購物車物件時,指定該購物車的商家資訊,以及構建購物車明細物件和購物車明細列表
將購物車的明細物件新增到購物車明細列表中,將購物車明細列表新增到購物車物件,在將
購物車物件新增到購物車列表中
2.商家對應的購物車物件存在於購物車列表中
判斷該商品是否存在於該購物車商品的明細列表中
1.如果該商品不存在購物車明細列表中
則建立購物車明細物件,在新增到購物車明細裡列表中
2.如果該商品存在購物車明細列表中
修改購物車明細物件的數量和小計金額
*/
@Override
public List<Cart> addItemToCartList(List<Cart> cartList, Long itemId, Integer num) {
//首先根據商品的id查詢 該商家是否存在於購物車中
TbItem item = itemMapper.selectByPrimaryKey(itemId);
//優化操作,新增購物車的時候,剛好該商品下架了
if(item==null){
throw new RuntimeException("商品不存在");
}
//如果商品的狀態不為1則無效
if (!item.getStatus().equals("1")){
throw new RuntimeException("商品無效");
}
String sellerId = item.getSellerId();
//在購物車列表中基於商品id 查詢購物車物件
Cart cart = searchCartBySellerId(cartList,sellerId);
//判斷購物車是否在空
if (cart==null){//購物車為空
//建立購物車物件,
cart = new Cart();
//指定該購物車的商家資訊,以及構建購物車明細物件和購物車明細列表
cart.setSellerId(sellerId);
cart.setSellerName(item.getSeller());
//以及構建購物車明細物件和購物車明細列表
List<TbOrderItem> orderItemList = new ArrayList<>();
//構建商品明細物件
TbOrderItem orderItem = createOrderItem(item,num);
//購物車的明細物件新增到購物車明細列表中
orderItemList.add(orderItem);
//購物車明細列表新增到購物車物件
cart.setOrderItemList(orderItemList);
//購物車物件新增到購物車列表中
cartList.add(cart);
}else{//該商品的存在
//先通過購物車獲取當前購物車的商品明細物件
List<TbOrderItem> orderItemList = cart.getOrderItemList();
//判斷該商品是否存在於該購物車商品的明細列表中
TbOrderItem orderItem = searchOrderIdByItemId(orderItemList,itemId);
if (orderItem==null){//在商品列表中不存在
//如果該商品不存在購物車明細列表中
//則建立購物車明細物件,在新增到購物車明細裡列表中
orderItem = createOrderItem(item,num);
//在新增到購物車明細裡列表中
orderItemList.add(orderItem);
}else{//存在於商品列表中
//修改購物車明細物件的數量和小計金額
orderItem.setNum(orderItem.getNum()+num);
//小計金額
orderItem.setTotalFee(new BigDecimal(orderItem.getPrice().doubleValue()*orderItem.getNum()));
//如果商品數量小於1 則刪除該商品
if(orderItem.getNum()<1){
orderItemList.remove(orderItem);
}
//如果購物車商品明細列表中沒有商品了, 則直接從購物車裡列表中移除
if (orderItemList.size()<=0){
cartList.remove(cart);
}
}
}
return cartList;
}
//判斷該商品是否存在於該購物車商品的明細列表中
private TbOrderItem searchOrderIdByItemId(List<TbOrderItem> orderItems, Long itemId) {
for (TbOrderItem orderItem : orderItems) {
if(orderItem.getItemId().longValue()==itemId.longValue()){//存在商品列表中
return orderItem;
}
}
return null;
}
//建立商品的明細物件
private TbOrderItem createOrderItem(TbItem item, Integer num) {
//優化操作
//建立商品的數量如果為負數
if(num<1){
throw new RuntimeException("新新增商品到購物車,商品數量不能小於1");
}
/*
`item_id` bigint(20) NOT NULL COMMENT '商品id',
`goods_id` bigint(20) DEFAULT NULL COMMENT 'SPU_ID',
`title` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '商品標題',
`price` decimal(20,2) DEFAULT NULL COMMENT '商品單價',
`num` int(10) DEFAULT NULL COMMENT '商品購買數量',
`total_fee` decimal(20,2) DEFAULT NULL COMMENT '商品總金額',
`pic_path` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '商品圖片地址',
`seller_id` varchar(100) COLLATE utf8_bin DEFAULT NULL,
*/
TbOrderItem orderItem = new TbOrderItem();
orderItem.setItemId(item.getId());
orderItem.setGoodsId(item.getGoodsId());
orderItem.setTitle(item.getTitle());
orderItem.setPrice(item.getPrice());
orderItem.setNum(num);
orderItem.setTotalFee(new BigDecimal(item.getPrice().doubleValue()*num));
orderItem.setPicPath(item.getImage());
orderItem.setSellerId(item.getSellerId());
return orderItem;
}
//根據商品的id查詢是否存在購物車物件
private Cart searchCartBySellerId(List<Cart> cartList, String sellerId) {
for (Cart cart : cartList) {
if (cart.getSellerId().equals(sellerId)){
return cart;
}
}
return null;
}
//通過sessionid獲得購物車列表
@Override
public List<Cart> selectCartListFromRedis(String sessionId) {
//從redis中獲取
List<Cart> cartList = (List<Cart>) redisTemplate.boundValueOps(sessionId).get();
//判斷cartList是否為空,因為你要返回一個list列表,如果為空,前臺就不能通過fastJson解析了
if (cartList==null){
//為空我們可以直接從新建立一個新的arrayList
cartList = new ArrayList<>();
}
return cartList;
}
//儲存購物車列表到redis中
@Override
public void saveCartListToRedis(String sessionId, List<Cart> cartList) {
redisTemplate.boundValueOps(sessionId).set(cartList,7L, TimeUnit.DAYS);
}
//合併登陸前購物車的資料到登陸後的購物車資料中
@Override
public List<Cart> mergeCartList(List<Cart> cartList_sessionId, List<Cart> cartList_username) {
//注意我們只需要構建我們需要新增的資料,因為我們在上面已經做過判斷,我們只需要,拼裝資料即可
for (Cart cart : cartList_sessionId) {
//獲取購車列表
List<TbOrderItem> itemList = cart.getOrderItemList();
for (TbOrderItem orderItem : itemList) {
//獲取num和ItemId的值
Integer num = orderItem.getNum();
Long itemId = orderItem.getItemId();
//購物車列表我們直接用登陸的就可以了
cartList_username= addItemToCartList(cartList_username,itemId,num);
}
}
return cartList_username;
}
//刪除登陸前購物車的資料
@Override
public void deleteCartList(List<Cart> cartList_sessionId) {
redisTemplate.delete(cartList_sessionId);
}
}
controller層:
思路分析:我們主要做兩件事 1.購物車列表資料的展示 2.新增商品到購物車列表中 還有一個非常重要就是獲取sessionid的方法,我們瀏覽器的存在的cookie是一次會話,我們想要使用者的商品儲存一週,我們必須後臺進行,重新建立cookie,設定為一週過期,通過一個工具類
CookieUtil來獲取和儲存
@RestController
@RequestMapping("/cart")
public class CartController {
@Autowired
private HttpSession session;
@Autowired
private HttpServletRequest request;
@Autowired
private HttpServletResponse response;
@Reference
private CartService cartService;
/**
* 獲得sessionid的方法
*/
private String getSessionId(){
//先嚐試從"cartCookie"中獲得sessionId資訊
String sessionId = CookieUtil.getCookieValue(request, "cartList", "utf-8");
if (sessionId==null){
//在從瀏覽器中獲取sessionid
sessionId = session.getId();
//將瀏覽器獲取的sessionId儲存一週
CookieUtil.setCookie(request,response,"cartList",sessionId,3600*24*7,"utf-8");
}
return sessionId;
}
/**
* 展示購物車列表資料
*/
@RequestMapping("/findCartList")
public List<Cart> findCartList(){
//獲取登陸人使用者名稱
String username = SecurityContextHolder.getContext().getAuthentication().getName();
//未登陸時,基於sessionid從redis中獲取購物車資料列表
String sessionId = getSessionId();
//從redis中獲取
List<Cart> cartList_sessionId = cartService.selectCartListFromRedis(sessionId);
if ("anonymousUser".equals(username)){//未登陸
System.out.println("selectCartListFromRedis by sessionId....");
return cartList_sessionId;
}else{//已登入
System.out.println("selectCartListFromRedis by username....");
List<Cart> cartList_username = cartService.selectCartListFromRedis(username);
//使用者登入前,如果已經新增商品到購物車列表中。
if(cartList_sessionId!=null&&cartList_sessionId.size()>0){//說明新增前已經存在商品了
//登陸後,將登陸前的購物車列表資料合併到登陸後的購物車列表中
cartList_username = cartService.mergeCartList(cartList_sessionId,cartList_username);
//將合併後的結果從新放到快取中
cartService.saveCartListToRedis(username,cartList_username);
//清除合併前的購物車列表資料
cartService.deleteCartList(cartList_sessionId);
}
return cartList_username;
}
}
/**
* 新增商品到購物車
*/
@RequestMapping("/addItemToCartList")
public Result addItemToCartList(Long itemId, Integer num){
try {
//獲取登陸人使用者名稱
String username = SecurityContextHolder.getContext().getAuthentication().getName();
System.out.println(username);
//獲取sessionId
String sessionId = getSessionId();
//1.查詢購物車列表
List<Cart> cartList = findCartList();
if ("anonymousUser".equals(username)){//未登陸
System.out.println("saveCartListToRedis by sessionId.....");
//2.新增商品到購物車
cartList = cartService.addItemToCartList(cartList,itemId, num);
//3.儲存購物車列表到redis中
cartService.saveCartListToRedis(sessionId,cartList);
}else{//已登入
System.out.println("saveCartListToRedis by username.....");
cartService.saveCartListToRedis(username,cartList);
}
return new Result(true,"新增購物車成功");
} catch (RuntimeException e) {
e.printStackTrace();
return new Result(false,e.getMessage());
}
catch (Exception e) {
e.printStackTrace();
return new Result(false,"新增購物車失敗");
}
}
}
前臺實現:
service層:
//服務層
app.service('cartService',function($http){
//查詢購物車列表資料
this.findCartList=function(){
return $http.get('cart/findCartList.do');
}
//查詢購物車列表資料
this.addItemToCartList=function (itemId,num) {
return $http.get('cart/addItemToCartList.do?itemId='+itemId+'&num='+num)
}
});
controller層:
//控制層
app.controller('cartController' ,function($scope,$controller ,cartService){
$controller('baseController',{$scope:$scope});//繼承
//讀取列表資料繫結到表單中
$scope.findCartList=function(){
cartService.findCartList().success(
function(response){
$scope.cartList=response;
sum();
}
);
}
//新增商品到購物車
$scope.addItemToCartList=function (itemId,num) {
cartService.addItemToCartList(itemId,num).success(function (response) {
if (response.success){
//新增購物車工程
$scope.findCartList();
}else{
//新增購物車失敗
alert(response.message)
}
})
}
//統計商品的數量和總計
sum=function () {
//總數量 和總金額
$scope.totalNum = 0;
$scope.totalMoney=0.00;
//遍歷購物車列表
for (var i = 0;i<$scope.cartList.length;i++){
//獲取購物車物件
var cart = $scope.cartList[i];
var orderItemList = cart.orderItemList;//獲取商品明細列表
//遍歷商品購物車明細列表
for(var j = 0;j<orderItemList.length;j++){
$scope.totalNum = orderItemList[i].num;
$scope.totalMoney= orderItemList[i].totalFee;
}
}
}
});
1.頁面的設定:
注意:如果我們未登陸,則要新增購物車,我們需要獲取使用者名稱:則通過一個spring-security.xml的配置
//之前增加的放行,刪掉,否則所有訪問cart下的訪問都不進入到springSecurity中
<http pattern="/cart/*.do" security="none"></http>
//但是我們需要購物車可以不登入也能增加到購物車中的操作,IS_AUTHENTICATED_ANONYMOUSLY和上面的配置區別在於,可以匿名訪問springSecurity(加在入口點引用中,注意攔截規則小範圍的在上面)
<intercept-url pattern="/cart/*.do" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
//在此攔截器上<intercept-url pattern="/**" access="ROLE_USER"/>
2.頁面跳轉的問題
思路分析:我們設定一個login.html頁面這個頁面就做一件事,就是用於轉發跳轉到cart.html頁面,因為我們在spring-sceurity.xml中沒有放行,所以會先進入cas登陸介面,我們登陸後,會轉發到cart.html頁面
程式碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>跳轉頁面</title>
<script type="text/javascript">
location.href="cart.html";
</script>
</head>
<body>
</body>
</html>