购物车是电商项目常用的功能,传统的做法可以使用关系型数据库,比如mysql来处理。但在实际使用中,由于购物车的数据量太大,而且修改频繁,会导致数据库的压力增加,所以一般不会直接使用关系型数据库来存储购物车信息。

既然不用关系型数据库,那么很多人就会选择mongodb或者redis来实现存放购物车信息,但考虑到性能方面来说,redis的方案更好。下面就聊聊如何使用redis来完成购物车的思路。

1、redis持久化和集群在redis的配置文件中,增加AOF的相关配置:

代码语言:javascript复制appendonly yes # 是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这种方式在许多应用中已经足够用了

appendfilename "appendonly.aof" # AOF 持久化文件名称

appendfsync everysec # appendfsync aof持久化策略的配置

# no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。

# always表示每次写入都执行fsync,以保证数据同步到磁盘。

# everysec表示每秒执行一次fsync,可能会导致丢失这1秒的数据。

关于redis的集群搭建可以自行搜索。

2、业务分析以京东购物车为例,按业务分析,需要完成如下功能:

1、全选功能-获取所有该用户的所有购物车商品2、商品数量-购物车图标上要显示的购物车里商品的总数3、删除-要能移除购物车里某个商品4、增加或减少某个商品sku的数量5、挑选最合适的优惠券注:京东的不需要登录就可以添加到购物车的,这采用的前端的缓存。本文主要是登录之后的后端操作。

2、数据结构选择redis支持多种数据类型,比如string,hash,list,set,zset等。针对于购物车需求,明显选择hash来做更合适。

HSET 将哈希表 key(用户id) field(skuId) value(SKU数量,是否选中,添加时间等)

代码语言:javascript复制redis 127.0.0.1:6379> HSET cart:userId skuId "{\"shopId\":\"123123\",\"skuId\":\"342342342\",\"num\":2,\"selected\":1,\"addTime\":123123213123}"

下面设计一下保存到redis中的购物车相关数据结构:

购物车商品实体:

代码语言:javascript复制@Data

@ApiModel("购物车-商品-缓存实体")

public class CartItem {

@ApiModelProperty(value = "店铺id")

private String shopId;

@ApiModelProperty(value = "skuId")

private String skuId;

@ApiModelProperty(value = "商品数量")

private Integer num;

@ApiModelProperty(value = "是否选中,1是,0否")

private Integer selected;

@ApiModelProperty(value = "添加到购物车时间")

private String addTime;

@ApiModelProperty(value = "修改购物车时间")

private String updateTime;

}

3、添加商品添加某商品SKU到购物车中需要考虑是否已存在该sku,如果存在直接将数量往上加即可。

代码语言:javascript复制 private static final String CART_KEY = "wechat:cart:";

public void addCart(Long userId , CartItem cartItem) {

if (cartItem.getNum() <= 0) {

throw new ApiException("保存购物车商品数量需大于0");

}

if (goodsNum(userId) >= MAX_GOODS) {

throw new ApiException("购物车最多添加99件商品");

}

try {

String key = CART_KEY + userId;

String cartItemStr = (String) redisTemplate.opsForHash().get(key, cartItem.getSkuId());

CartItem item = JSON.parseObject(cartItemStr, CartItem.class);

if (ObjectUtil.isNotNull(item) && ObjectUtil.isNotNull(item.getSkuId())) {

// 已存在商品 直接修改数量

item.setNum(checkStock(item.getNum() + cartItem.getNum() , Long.parseLong(cartItem.getSkuId())));

item.setUpdateTime(String.valueOf(System.currentTimeMillis()));

redisTemplate.opsForHash().put(key , cartItem.getSkuId(), JSON.toJSONString(item));

}else {

// 不存在商品

cartItem.setNum(checkStock(cartItem.getNum() , Long.parseLong(cartItem.getSkuId())));

redisTemplate.opsForHash().put(key , cartItem.getSkuId(), JSON.toJSONString(cartItem));

}

}catch (ApiException e) {

throw e;

}catch (Exception e) {

e.printStackTrace();

log.error("添加购物车缓存失败 用户id {} , cartItem {} , 错误信息: {}" , userId , cartItem , e.getMessage());

throw new ApiException(ResultCode.ERROR.getMessage());

}

}

4、删除商品删除购物车商品,直接根据用户id和商品skuId进行删除即可。除了用户手动删除购物车中指定商品,还会在下单的时候需要删除购物车中对应的商品。

代码语言:javascript复制 /**

* 删除购物车商品

*/

public void deleteCart(List skuIds , Long userId) {

try {

String key = CART_KEY + userId;

redisTemplate.opsForHash().delete(key , skuIds.stream().map(String::valueOf).toArray());

}catch (Exception e) {

log.error("删除购物车缓存失败 用户id {} , skuId {} , 错误信息: {}" , userId , skuIds, e.getMessage());

throw new ApiException(ResultCode.ERROR.getMessage());

}

}

5、修改商品修改购物车中的商品主要涉及修改商品SKU的数量、替换了该商品的其他规格SKU.

代码语言:javascript复制

public void updateSkuCart(Long userId , Long oldSkuId , Long newSkuId , int goodsNum) {

try {

if (goodsNum <= 0) {

return;

}

String key = CART_KEY + userId;

String jsonStr = (String) redisTemplate.opsForHash().get(key, oldSkuId.toString());

CartItem cartItem = JSON.parseObject(jsonStr, CartItem.class);

if (ObjectUtil.isNull(cartItem) || ObjectUtil.isNull(cartItem.getSkuId())) {

throw new ApiException("该商品不存在");

}

if (oldSkuId.equals(newSkuId)) {

// 修改自己数量

cartItem.setNum(checkStock(goodsNum , oldSkuId));

cartItem.setUpdateTime(String.valueOf(System.currentTimeMillis()));

redisTemplate.opsForHash().put(key , cartItem.getSkuId() , JSON.toJSONString(cartItem));

}else {

// 修改其他商品

String newJsonStr = (String) redisTemplate.opsForHash().get(key, newSkuId.toString());

CartItem newCartItem = JSON.parseObject(newJsonStr, CartItem.class);

if (ObjectUtil.isNull(newCartItem) || ObjectUtil.isNull(newCartItem.getSkuId())) {

// 添加购物车商品不存在

cartItem.setSkuId(newSkuId.toString());

cartItem.setNum(checkStock(goodsNum , newSkuId));

cartItem.setUpdateTime(String.valueOf(System.currentTimeMillis()));

redisTemplate.opsForHash().put(key , newSkuId.toString() , JSON.toJSONString(cartItem));

}else {

// 添加购物车商品已经存在 修改数量

newCartItem.setNum(checkStock(newCartItem.getNum() + goodsNum , newSkuId));

newCartItem.setUpdateTime(String.valueOf(System.currentTimeMillis()));

redisTemplate.opsForHash().put(key , newSkuId.toString() , JSON.toJSONString(newCartItem));

}

redisTemplate.opsForHash().delete(key , oldSkuId.toString()); // 把旧的商品删除

}

}catch (ApiException e){

log.error("修改购物车失败userId {} , skuId {} , 错误信息: {}" , userId , oldSkuId , e.getMessage());

e.printStackTrace();

throw e;

}catch (Exception e){

log.error("修改购物车失败userId {} , skuId {} , 错误信息: {}" , userId , oldSkuId , e.getMessage());

e.printStackTrace();

throw new ApiException(ResultCode.ERROR.getMessage());

}

}

private Integer checkStock(Integer num , Long skuId) {

YyShopGoodsSku yyShopGoodsSku = yyShopGoodsSkuMapper.selectByPrimaryKey(skuId);

if (yyShopGoodsSku.getStock().equals(0)) {

throw new ApiException(ResultCode.GOODS_STOCK_INSUFFICIENT.getMessage());

}

return yyShopGoodsSku.getStock() < num ? yyShopGoodsSku.getStock() : num;

}

6、获取商品列表获取购物车的列表,可以通过用户id以及是否选中来进行筛选。

代码语言:javascript复制 /**

* 获取购物车 key-skuId value-CartItem

*/

public Map listCart(Long userId,Integer selected) {

String rkey = CART_KEY + userId;

try(Cursor> cursor = redisTemplate.opsForHash().scan(rkey ,

ScanOptions.NONE)) {

Map resultMap = new HashMap<>();

while (cursor.hasNext()) {

Map.Entry next = cursor.next();

Long key = Long.parseLong((String) next.getKey());

String value = (String) next.getValue();

if (ObjectUtil.isNull(key) || StringUtils.isBlank(value)) {

continue;

}

CartItem cartItem = JSON.parseObject(value, CartItem.class);

if (ObjectUtil.isNull(selected) || ObjectUtil.equal(cartItem.getSelected(),selected)){

resultMap.put(key , JSON.parseObject(value , CartItem.class));

}

}

return resultMap;

}catch (Exception e) {

log.error("查询购物车缓存失败 用户id {} , 错误信息: {}" , userId , e.getMessage());

throw new ApiException(ResultCode.ERROR.getMessage());

}

}

7、获取商品数量代码语言:javascript复制 /**

* 获取购物车商品数量

*/

public int goodsNum(Long userId) {

String key = CART_KEY + userId;

Long num = redisTemplate.opsForHash().size(key);

return ObjectUtil.isNull(num) ? 0 : num.intValue();

}

参考文章https://wenku.baidu.com/view/89ea17dbcbd376eeaeaad1f34693daef5ff71357.html