购物车存储

购物车

在很多电商系统中,用户不会一件一件的买商品,不是同一店铺,都是先加入购物车,然后去生成订单一起支付。购物车的概念对应超市中的购物车。

购物车的存储

存储至数据库(mysql)

数据库有连接上、存储上的瓶颈。

前端存储至localstorage 或者sessionstorage

localstorage 没有过期时间

sessionstorage关闭浏览器后消失。

存储至缓存(redis)

因为是缓存会有可能会丢失,但是购物车的业务有丢失也可以忍受。

存储至缓存(redis),缓存同步至数据库(mysql)

只有特殊需求时才需要,用到数据库的查询与统计能力之类。

存储至缓存(redis)实现

1
2
3
4
5
6
7
8
9
10
11
12
@ApiModel
@Data
public class CartItemRequest {

@ApiModelProperty(value = "商品id",example = "11")
@JsonProperty("product_id")
private long productId;

@ApiModelProperty(value = "购买数量",example = "1")
@JsonProperty("buy_num")
private int buyNum;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class CartVO {

/**
* 购物项
*/
@JsonProperty("cart_items")
private List<CartItemVO> cartItems;


/**
* 购买总件数
*/
@JsonProperty("total_num")
private Integer totalNum;

/**
* 购物车总价格
*/
@JsonProperty("total_amount")
private BigDecimal totalAmount;

/**
* 购物车实际支付价格
*/
@JsonProperty("real_pay_amount")
private BigDecimal realPayAmount;


/**
* 总件数
* @return
*/
public Integer getTotalNum() {
if(this.cartItems!=null){
int total = cartItems.stream().mapToInt(CartItemVO::getBuyNum).sum();
return total;
}
return 0;
}

/**
* 总价格
* @return
*/
public BigDecimal getTotalAmount() {
BigDecimal amount = new BigDecimal("0");
if(this.cartItems!=null){
for(CartItemVO cartItemVO : cartItems){
BigDecimal itemTotalAmount = cartItemVO.getTotalAmount();
amount = amount.add(itemTotalAmount);
}
}
return amount;
}

/**
* 购物车里面实际支付的价格
* @return
*/
public BigDecimal getRealPayAmount() {
BigDecimal amount = new BigDecimal("0");
if(this.cartItems!=null){
for(CartItemVO cartItemVO : cartItems){
BigDecimal itemTotalAmount = cartItemVO.getTotalAmount();
amount = amount.add(itemTotalAmount);
}
}
return amount;
}

public List<CartItemVO> getCartItems() {
return cartItems;
}

public void setCartItems(List<CartItemVO> cartItems) {
this.cartItems = cartItems;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
public class CartItemVO {

/**
* 商品id
*/
@JsonProperty("product_id")
private Long productId;


/**
* 购买数量
*/
@JsonProperty("buy_num")
private Integer buyNum;

/**
* 商品标题
*/
@JsonProperty("product_title")
private String productTitle;

/**
* 图片
*/
@JsonProperty("product_img")
private String productImg;

/**
* 商品单价
*/
private BigDecimal amount;

/**
* 总价格,单价+数量
*/
@JsonProperty("total_amount")
private BigDecimal totalAmount;


public Long getProductId() {
return productId;
}

public void setProductId(Long productId) {
this.productId = productId;
}

public Integer getBuyNum() {
return buyNum;
}

public void setBuyNum(Integer buyNum) {
this.buyNum = buyNum;
}

public String getProductTitle() {
return productTitle;
}

public void setProductTitle(String productTitle) {
this.productTitle = productTitle;
}

public String getProductImg() {
return productImg;
}

public void setProductImg(String productImg) {
this.productImg = productImg;
}

public BigDecimal getAmount() {
return amount;
}

public void setAmount(BigDecimal amount) {
this.amount = amount;
}

/**
* 商品单价 * 购买数量
* @return
*/
public BigDecimal getTotalAmount() {

return this.amount.multiply(new BigDecimal(this.buyNum));
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
@Service
@Slf4j
public class CartServiceImpl implements CartService {

@Autowired
private ProductService productService;

@Autowired
private RedisTemplate redisTemplate;


@Override
public void addToCart(CartItemRequest cartItemRequest) {

long productId = cartItemRequest.getProductId();
int buyNum = cartItemRequest.getBuyNum();

//获取购物车
BoundHashOperations<String,Object,Object> myCart = getMyCartOps();

Object cacheObj = myCart.get(productId);
String result = "";

if(cacheObj!=null){
result = (String)cacheObj;
}

if(StringUtils.isBlank(result)){
//不存在则新建一个商品
CartItemVO cartItemVO = new CartItemVO();

ProductVO productVO = productService.findDetailById(productId);
if(productVO == null){throw new BizException(BizCodeEnum.CART_FAIL);}

cartItemVO.setAmount(productVO.getAmount());
cartItemVO.setBuyNum(buyNum);
cartItemVO.setProductId(productId);
cartItemVO.setProductImg(productVO.getCoverImg());
cartItemVO.setProductTitle(productVO.getTitle());
myCart.put(productId,JSON.toJSONString(cartItemVO));

}else {
//存在商品,修改数量
CartItemVO cartItem = JSON.parseObject(result,CartItemVO.class);
cartItem.setBuyNum(cartItem.getBuyNum()+buyNum);
myCart.put(productId,JSON.toJSONString(cartItem));
}

}


/**
* 清空购物车
*/
@Override
public void clear() {
String cartKey = getCartKey();
redisTemplate.delete(cartKey);

}


/**
* 删除购物项
* @param productId
*/
@Override
public void deleteItem(long productId) {

BoundHashOperations<String,Object,Object> mycart = getMyCartOps();

mycart.delete(productId);

}



@Override
public void changeItemNum(CartItemRequest cartItemRequest) {
BoundHashOperations<String,Object,Object> mycart = getMyCartOps();

Object cacheObj = mycart.get(cartItemRequest.getProductId());

if(cacheObj==null){throw new BizException(BizCodeEnum.CART_FAIL);}

String obj = (String)cacheObj;

CartItemVO cartItemVO = JSON.parseObject(obj,CartItemVO.class);
cartItemVO.setBuyNum(cartItemRequest.getBuyNum());
mycart.put(cartItemRequest.getProductId(),JSON.toJSONString(cartItemVO));
}


@Override
public CartVO getMyCart() {

//获取全部购物项
List<CartItemVO> cartItemVOList = buildCartItem(false);

//封装成cartvo
CartVO cartVO = new CartVO();
cartVO.setCartItems(cartItemVOList);

return cartVO;
}


/**
* 获取最新的购物项,
* @param latestPrice 是否获取最新价格
* @return
*/
private List<CartItemVO> buildCartItem(boolean latestPrice) {

BoundHashOperations<String,Object,Object> myCart = getMyCartOps();

List<Object> itemList = myCart.values();

List<CartItemVO> cartItemVOList = new ArrayList<>();

//拼接id列表查询最新价格
List<Long> productIdList = new ArrayList<>();

for(Object item: itemList){
CartItemVO cartItemVO = JSON.parseObject((String)item,CartItemVO.class);
cartItemVOList.add(cartItemVO);

productIdList.add(cartItemVO.getProductId());
}

//查询最新的商品价格
if(latestPrice){

setProductLatestPrice(cartItemVOList,productIdList);
}

return cartItemVOList;

}

/**
* 设置商品最新价格
* @param cartItemVOList
* @param productIdList
*/
private void setProductLatestPrice(List<CartItemVO> cartItemVOList, List<Long> productIdList) {

//批量查询
List<ProductVO> productVOList = productService.findProductsByIdBatch(productIdList);

//分组
Map<Long,ProductVO> maps = productVOList.stream().collect(Collectors.toMap(ProductVO::getId,Function.identity()));


cartItemVOList.stream().forEach(item->{

ProductVO productVO = maps.get(item.getProductId());
item.setProductTitle(productVO.getTitle());
item.setProductImg(productVO.getCoverImg());
item.setAmount(productVO.getAmount());

});


}


/**
* 抽取我的购物车,通用方法
* @return
*/
private BoundHashOperations<String,Object,Object> getMyCartOps(){
String cartKey = getCartKey();
return redisTemplate.boundHashOps(cartKey);
}


/**
* 购物车 key
* @return
*/
private String getCartKey(){
LoginUser loginUser = LoginInterceptor.threadLocal.get();
String cartKey = String.format(CacheKey.CART_KEY,loginUser.getId());
return cartKey;

}
}

购物车存储
http://hanqichuan.com/2023/10/19/系统设计/购物车存储/
作者
韩启川
发布于
2023年10月19日
许可协议