前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >购物车设计与实现

购物车设计与实现

作者头像
用户3467126
发布2022-04-28 20:40:35
1.7K0
发布2022-04-28 20:40:35
举报
文章被收录于专栏:爱编码

购物车是电商项目常用的功能,传统的做法可以使用关系型数据库,比如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<Long> 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<Long , CartItem> listCart(Long userId,Integer selected) {
        String rkey = CART_KEY + userId;
        try(Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(rkey ,
                ScanOptions.NONE)) {
            Map<Long , CartItem> resultMap = new HashMap<>();
            while (cursor.hasNext()) {
                Map.Entry<Object, Object> 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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱编码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、redis持久化和集群
  • 2、业务分析
  • 2、数据结构选择
  • 3、添加商品
  • 4、删除商品
  • 5、修改商品
  • 6、获取商品列表
  • 7、获取商品数量
  • 参考文章
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档