Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >购物车设计与实现

购物车设计与实现

作者头像
用户3467126
发布于 2022-04-28 12:40:35
发布于 2022-04-28 12:40:35
1.9K00
代码可运行
举报
文章被收录于专栏:爱编码爱编码
运行总次数:0
代码可运行

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

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

1、redis持久化和集群

在redis的配置文件中,增加AOF的相关配置:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
redis 127.0.0.1:6379> HSET cart:userId skuId "{\"shopId\":\"123123\",\"skuId\":\"342342342\",\"num\":2,\"selected\":1,\"addTime\":123123213123}"

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

购物车商品实体:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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
代码运行次数:0
运行
AI代码解释
复制
    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
代码运行次数:0
运行
AI代码解释
复制
   /**
     * 删除购物车商品
     */
    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
代码运行次数:0
运行
AI代码解释
复制


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
代码运行次数:0
运行
AI代码解释
复制
  /**
     * 获取购物车 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
代码运行次数:0
运行
AI代码解释
复制
   /**
     * 获取购物车商品数量
     */
    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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Redis应用—3.在购物车里的应用
2.购物车的复杂缓存与异步落库(Sorted Set + Hash -> hPut + zadd)
东阳马生架构
2025/03/04
1390
商城项目-已登录购物车
购物车系统只负责登录状态的购物车处理,因此需要添加登录校验,我们通过JWT鉴权即可实现。
cwl_java
2020/01/14
9921
商城项目-已登录购物车
商城业务:购物车
- 用户可以在未登录状态下将商品添加到购物车【游客购物车/离线购物车/临时购物车】
一个风轻云淡
2023/10/15
3800
商城业务:购物车
【第十八篇】商城系统-订单中心设计解决方案
  我们需要把相关的静态资源拷贝到nginx,然后动态模板文件拷贝到order项目的templates目录下,然后调整资源的路径。在网关中设置对应的路由即可。
用户4919348
2022/10/04
7580
【第十八篇】商城系统-订单中心设计解决方案
谷粒商城-高级篇(购物车)
​ 参考京东,在点击购物车时,会为临时用户生成一个name为user-key的cookie临时标识,过期时间为一个月,如果手动清除user-key,那么临时购物车的购物项也被清除,所以 user-key 是用来标识和存储临时购物车数据的
OY
2022/03/20
7480
谷粒商城-高级篇(购物车)
深入解析Java中如何用Redis存储购物车信息:原理与实战案例
咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE相关知识点了,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~
bug菌
2024/10/24
4120
深入解析Java中如何用Redis存储购物车信息:原理与实战案例
【第十七篇】商城系统-购物车功能设计
  Nginx接收了 cart.msb.com这个域名的访问,那么会把服务反向代理给网关服务,这时网关服务就需要把该请求路由到购物车服务中。我们需要修改网关服务的配置
用户4919348
2022/10/04
8890
【第十七篇】商城系统-购物车功能设计
【畅购商城】购物车模块之修改购物车以及结算
目录 购物车操作:修改 分析 接口 后端实现:更新 前端实现:修改 前端实现:全选 后端实现:删除数据 结算 跳转页面 购物车操作:修改 分析 接口 PUT http://localhost:10010/cart-service/carts 后端实现:更新 步骤一:修改service接口 /** * 更新操作:如果数据存在修改数据,如果数据不存在删除数据 * @param user * @param cartVoList */ public void updateCart(User
陶然同学
2023/02/24
1.1K0
【畅购商城】购物车模块之修改购物车以及结算
【畅购商城】购物车模块之添加购物车
步骤二:修改Goods.vue,给“加入购物车”绑定点击事件 addToCartFn
陶然同学
2023/02/24
2.5K0
【畅购商城】购物车模块之添加购物车
如何一步一步用DDD设计一个电商网站(十)—— 一个完整的购物车
之前的文章中已经涉及到了购买商品加入购物车,购物车内购物项的金额计算等功能。本篇准备把剩下的购物车的基本概念一次处理完。
Zachary_ZF
2018/09/10
9010
如何一步一步用DDD设计一个电商网站(十)—— 一个完整的购物车
购物车需求分析与解决方案
目标1:说出品优购购物车的实现思路 目标2:运用Cookie存储购物车 目标3:编写购物车前端代码 目标4:运用Redis存储购物车
用户1212940
2022/04/13
1.1K0
购物车需求分析与解决方案
Spring高级技术应用——百战商城实现(下)
需要用到pojo,但是我们可以通过依赖Mapper项目来简介添加Pojo项目 需要用到Spring Data整合Solr的坐标
时间静止不是简史
2020/07/27
1.2K0
Spring高级技术应用——百战商城实现(下)
前端购物车&订单结算模块详解
首先, 我们需要在vant中找到对应的组件, 这里是ActionSheet组件。 通过对ActionSheet组件的修改, 从而得到我们需要的内容。
用户11097514
2024/05/31
8810
前端购物车&订单结算模块详解
购物车的原理以及实现
  今天模拟京东的购物车实现原理完成了购物车模块的开发, 给大家分享下。 京东的购物车实现原理:在用户登录和不登录的状态下对购物车存入cookie还是持久化到redis中的实现。下面就来具体说次购物车的实现过程 两种情况: 用户登录,购物车存入redis中 用户未登录,购物车存入cookie中 比较两种方式的优缺点:  cookie:优点:数据保存在用户浏览器中,不占用服务端内存;用户体检效果好;代码实现简单      缺点:cookie的存储空间只有4k;更换设备时,购物车信息不能同步;cookie禁用,
用户2146856
2018/05/18
2.5K0
【第二十篇】商城系统-秒杀功能设计与实现
  在购买商品的时候,进入到商品详情页,如果该商品也参与了秒杀活动,那么对应的需要展示相关的信息
用户4919348
2022/10/06
6460
【第二十篇】商城系统-秒杀功能设计与实现
购物车的原理及实现.(仿京东实现原理)
2018年1月20号更新: 这个博客是自己对着传智的视频一点点学习的, 敲完了一整遍代码感觉自己也学到挺多东西,现在好多小伙伴说链接失效了, 现在补上传智的整套视频和源码,有问题给我留言。 链接:https://pan.baidu.com/s/1c3MMv6o 密码:w9py 2017年7月14日更新:  有很多小伙伴想要项目资料和源码, 我重新整理了一份传了上来:  这次更新的为项目全套视频及所有源码资料: 链接:https://pan.baidu.com/s/1c3MMv6o 密码:w9py 今天来
一枝花算不算浪漫
2018/05/18
1.6K0
React Hook + TS 购物车实战(性能优化、闭包陷阱、自定义hook)
本文由一个基础的购物车需求展开,一步一步带你深入理解React Hook中的坑和优化
ssh_晨曦时梦见兮
2020/04/11
1.8K0
React Hook + TS 购物车实战(性能优化、闭包陷阱、自定义hook)
电商---实现购物车功能
优点:不占用服务器资源,可以永远保存,不用考虑失效的问题 缺点: 对购买商品的数量是有限制的,存放数据的大小 不可以超过2k,用户如果禁用cookie那么就木有办法购买商品,卓越网实现了用户当用户禁用cookie,也可以购买。
周小董
2019/03/25
1.9K0
干货 | 京东购物车的Java架构实现及原理!
4)用户登陆了用户名密码,添加商品, 关闭浏览器 外地老家打开浏览器  登陆用户名和密码
美的让人心动
2018/05/30
2.9K1
购物车系统设计
购物车对数据可靠性要求不高,性能也无特别要求,在整个电商系统是相对容易设计和实现的一个子系统。
JavaEdge
2022/12/15
1.3K0
购物车系统设计
推荐阅读
相关推荐
Redis应用—3.在购物车里的应用
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验