目录
购物车数据结构
由于购物车数据量小,且数据变化比较频繁,所以采用Redis内存数据库来存储,采用的数据类型如下:
- 存储商品数据,采用hash结构,如cart_1:{3:5}。其中的数字部分分别代表用户id,加购的商品id,购买的该商品的数量。
- 存储商品的选中状态,采用set结构,如cart_selected_1: {3, 5,…}
集合中的数字为勾选的商品id。
Redis hash&set数据类型操作回顾:
$ redis-cli -h localhost -p 6379
>ping
>hset cart_1 3 5
>hmset cart_1 5 2 6 3
>hkeys cart_1
>hvals cart_1
>hgetall cart_1
>hdel cart_1 3
>sadd cart_selected_1 3
>sadd cart_selected_1 5 7 9
>srem cart_selected_1 5
>smembers cart_selected_1
python的操作方法与以上类似
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
r.hset('cart_1', key=2, value=3)
r.hget("cart_1", 2)
r.hdel("cart_1", 2)
r.sadd("cart_selected_1", 2)
r.sadd("cart_selected_1", 2,3)
r.smembers("cart_selected_1")
r.srem("cart_selected_1", 2)
r.close()
添加购物车
前端需求分析(参考已上线的项目)
- 未登录时点击添加购物车
- 已登录时,点击添加购物车
2.1 该商品第一次加入购物车,在购物车中添加一个商品,并提示’添加购物车成功’
2.2 该商品已经在购物车,再次加购,提示’该商品已在购物车,数量+1′
2.3 达到加购上限,提示’加购达到限购数量!’ , 同时’加入购物车’按钮不可点击
2.4 完成加购后,右上角显示当前购物车中的商品总数。
加购前端接口分析
在Details组件中的模板找到如下代码:
<div class="button">
<el-button class="shop-cart" :disabled="dis" @click="addShoppingCart">加入购物车</el-button>
<el-button class="like" @click="addCollect">喜欢</el-button>
</div>
点击’加入购物车’会触发click事件,对应的函数如下:
addShoppingCart() {
if (!this.$store.getters.getUser) {
this.$store.dispatch("setShowLogin", true);
return;
}
console.log("@@getUser,已登录用户:",this.$store.getters.getUser)
this.$axios
.post("/carts/user/addCarts/", {
user: this.$store.getters.getUser.userName,
product_id: this.productID
})
.then(res => {
console.log("@@添加购物车res:", res)
switch (res.data.code) {
case 200:
this.unshiftShoppingCart(res.data.shoppingCartData);
this.notifySucceed(res.data.msg);
break;
case 201:
this.addShoppingCartNum(this.productID);
this.notifySucceed(res.data.msg);
break;
case 202:
this.dis = true;
this.notifyError(res.data.msg);
break;
default:
this.notifyError(res.data.msg);
}
})
.catch(err => {
return Promise.reject(err);
});
},
总结:
请求地址, /carts/user/addCarts/
请求方法,POST
提交的数据, {
user: this.$store.getters.getUser.userName,
productID: this.productID
}
需要的响应,
{
“code”:200,
“msg”:”新加入购物车成功”,
“shoppingCartData”:{
id: “”, // 购物车id, 如cart_1
productID: “”, // 商品id
productName: “”, // 商品名称
productImg: “”, // 商品图片
price: “”, // 商品价格
num: “”, // 购物车中该商品数量
maxNum: “”, // 商品限购数量,即库存
check: false // 是否勾选
}
}
{“code”:201, “msg”:”该商品已经在购物车,数量+1″}
{“code”:202, “msg”:”加购达到限购数量!”}
{‘code’:203, ‘msg’:’库存不足,无法购买!’}
{“code”:204, “msg”:’其他异常’}
加购后端思路
1. 获取前端数据{“user”:’laufing’, “productID”:3}
2. 根据用户名查询用户对象并构造key,根据商品id查询商品对象,以便获取库存。
3. hash====> cart_uid : {good_id:count, …}
set====> cart_selected_uid: {good_id, …}
存入数据:(加购数量count=1)
a. 如果库存量stock>0 ,则允许加购:
redis如果购物车历史中有该商品,则先获取历史总数num, 然后 num += count,最后比较num总数与库存量。若num
后端加购视图代码
path("user/addCarts/", AddCart.as_view()),
class AddCart(APIView):
"""
数据存入redis购物车
1. hash类型,表示购物车商品数据 cart_uid:{good_id:count}
2. set类型, 表示商品的选中状态(选中) cart_selected_uid: {good_id}
3. 存入redis之前判断该商品的库存量、它是否在历史购物车中、最终的加购总数num是否
def post(self, request):
try:
username = request.data.get("user")
good_id = request.data.get("productID")
count = 1
try:
user = User.objects.get(username=username)
cart_key = "cart_%s"%user.id
cart_selected_key = "cart_selected_%s"%user.id
except:
return Response({"code":204, "msg":"用户不存在!"})
try:
good = Goods.objects.get(id=good_id)
except:
return Response({"code":204, "msg":"商品不存在!"})
redis_conn = redis.Redis(host='localhost', port=6379, db=0)
if good.stock > 0:
num = redis_conn.hget(cart_key, good_id)
if num:
num = int(num.decode())
num += count
if num good.stock:
redis_conn.hset(cart_key, good_id, num)
redis_conn.sadd(cart_selected_key, good_id)
redis_conn.close()
return Response({"code":201, "msg":"该商品已经在购物车,数量+1"})
else:
redis_conn.close()
return Response({"code":202, "msg":"加购达到限购数量!"})
else:
redis_conn.hset(cart_key, good_id, count)
redis_conn.sadd(cart_selected_key, good_id)
shopping_cart_data = {
"id": cart_key,
"productID": good_id,
"productName": good.sku_name,
"productImg": good.img,
"price": good.selling_price,
"num": count,
"maxNum": good.stock,
"check": True,
}
redis_conn.close()
return Response({"code": 200, "msg": "新加入购物车成功!", "shoppingCartData": shopping_cart_data})
else:
redis_conn.close()
return Response({"code":203, "msg":"库存不足,无法购买!"})
except:
return Response({"code":204, "msg":"服务器内部错误"})
以上在浏览器控制台可以看到对应的响应。
查看购物车
- 购物车商品总数前端分析
用户登录后,在首页右上角的购物车处,显示加购商品总数。查看源代码可以发现,它是一个计算属性,如下:
getNum,从集中式状态管理中映射过来的 ,即store>getters>getNum。
该函数统计state.shoppingCart数组中的商品数量。
getNum (state) {
let totalNum = 0;
for (let i = 0; i < state.shoppingCart.length; i++) {
const temp = state.shoppingCart[i];
totalNum += temp.num;
}
return totalNum;
},
- 加载购物车商品数据前端分析
那么state中的shoppingCart数据什么时候加载的?
监视属性getUser,用户登录后state.user属性值即由” “变为”laufing”, 触发对应的函数
getUser: function (val) {
if (val === "") {
this.setShoppingCart([]);
} else {
this.$axios
.post("/carts/user/getShoppingCart/", {
user: val,
})
.then((res) => {
console.log("@@当前用户的购物车数据:",res)
if (res.data.code == 200) {
this.setShoppingCart(res.data.shoppingCartData);
} else {
this.notifyError(res.data.msg);
}
})
.catch((err) => {
return Promise.reject(err);
});
}
},
总结:
用户每次登陆时,state.user的值会发生变化,触发监视属性getUser对应的函数,从而向后端发送请求,加载购物车商品数据。
请求地址,/carts/user/getShoppingCart/
请求方法,POST
提交数据,{
user: val, //当前 用户对象 {userName:’laufing’}
}
需要的响应,{
code:200,
msg:’加载购物车商品数据成功!’
shoppingCartData:[{ },…]
}
其中的每个商品对象格式,
{
id: “”, // 购物车id
productID: “”, // 商品id
productName: “”, // 商品名称
productImg: “”, // 商品图片
price: “”, // 商品价格
num: “”, // 加购的商品数量
maxNum: “”, // 商品限购数量
check: false // 是否勾选
}
- 加载购物车商品数据后端视图
path('user/getShoppingCart/', GetCart.as_view()),
class GetCart(APIView):
"""
1. 根据用户名查询用户对象
2. 根据用户id构造key,查询redis中的购物车数据(cart_uid、cart_selected_uid)
3. 根据购物车里的商品id,查询商品对象,组织每一个商品字典
{
"id": "cart_uid",
"productID":xxx,
"productName":xxx,
"productImg":xxx,
"price":商品的selling_price,
"num": xxx, 加购数量
"maxNum": xxx, 限购数量
"check": True or False, 是否勾选
}
4.多个商品字典 组织为一个列表[{},{},...], 然后返回响应
"""
def post(self, request):
try:
username = request.data.get("user").get("userName")
try:
user = User.objects.get(username=username)
cart_key = "cart_%s"%user.id
cart_selected_key = "cart_selected_%s"%user.id
except:
return Response({"code":204, "msg":"当前用户不存在!"})
redis_conn = redis.Redis(host="localhost", port=6379, db=0)
shopping_cart_data = []
cart_selected = [int(i.decode()) for i in redis_conn.smembers(cart_selected_key)]
for good_id, count in redis_conn.hgetall(cart_key).items():
good_id = int(good_id.decode())
count = int(count.decode())
if good_id in cart_selected:
one_good = get_one_good(cart_key, good_id, count, check=True)
else:
one_good = get_one_good(cart_key, good_id, count, check=False)
shopping_cart_data.append(one_good)
return Response({"code":200, 'msg':"获取用户的购物车数据成功!",
"shoppingCartData":shopping_cart_data})
except:
return Response({"code":204, "msg":"服务端获取购物车数据异常"})
def get_one_good(cart_key, good_id, count, check=False):
"""
:param cart_key: 购物车id
:param good_id: redis购物车中一个商品的id,
:param count: 该商品加购的数量
:param check: 该商品是否被勾选
:return: 一个商品的字典数据
"""
try:
good = Goods.objects.get(id=good_id)
except:
return {}
return {
"id":cart_key,
"productID":good_id,
"productName":good.sku_name,
"productImg":good.img,
"price":good.selling_price,
"num":count,
"maxNum":good.stock,
"check": check,
}
- 前端展示购物车商品
未登录时,点击购物车(router-link),弹出登录框。
已登录时,点击购物车,则跳转到购物车页面。
前端代码分析,在APP组件中找到’购物车’
<router-link to="/shoppingCart">
<i class="el-icon-shopping-cart-full"></i> 购物车
<span class="num">({{ getNum }})</span>
</router-link>
路由/shoppingCart
组件ShoppingCart
在路由与组件的对应关系中,有requireAuth:true,所以每次未登录时点击购物车都会弹出登录框。
用户完成登录,vuex中的user从空值改为具体的用户对象,触发监视属性getUser对应的函数,去加载用户的购物车数据。
然后跳转到购物车页面,展示加载的购物车商品。
具体参考ShoppingCart组件
页面效果:
Original: https://blog.csdn.net/weixin_45228198/article/details/122730102
Author: laufing
Title: shoppe项目08—-购物车
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/736555/
转载文章受原作者版权保护。转载请注明原作者出处!