购物车业务

news2024/11/17 21:16:53

一、分析购物车vo

(1)添加成功页
在这里插入图片描述

public class CartItemVo implements Serializable {

    /**
     * 商品id
     */
    private Long skuId;

    /**
     * 是否选中
     */
    private Boolean check = true;

    /**
     * 商品标题
     */
    private String title;

    /**
     * 商品图片
     */
    private String image;

    /**
     * 商品属性
     */
    private List<String> skuAttrValues;

    /**
     * 商品价格
     */
    private BigDecimal price;

    /**
     * 商品数量
     */
    private Integer count;

    /**
     * 商品总价 = 价格 * 数量
     */
    private BigDecimal totalPrice;




    public Long getSkuId() {
        return skuId;
    }

    public void setSkuId(Long skuId) {
        this.skuId = skuId;
    }

    public Boolean getCheck() {
        return check;
    }

    public void setCheck(Boolean check) {
        this.check = check;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public List<String> getSkuAttrValues() {
        return skuAttrValues;
    }

    public void setSkuAttrValues(List<String> skuAttrValues) {
        this.skuAttrValues = skuAttrValues;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }

    /**
     * 计算当前购物项总价
     * @return
     */
    public BigDecimal getTotalPrice() {
        return this.price.multiply(new BigDecimal(this.count.toString()));
    }

    public void setTotalPrice(BigDecimal totalPrice) {
        this.totalPrice = totalPrice;
    }

    @Override
    public String toString() {
        return "CartItemVo{" +
                "skuId=" + skuId +
                ", check=" + check +
                ", title='" + title + '\'' +
                ", image='" + image + '\'' +
                ", skuAttrValues=" + skuAttrValues +
                ", price=" + price +
                ", count=" + count +
                ", totalPrice=" + totalPrice +
                '}';
    }
}

(2)结算页
在这里插入图片描述

public class Cart {

    /**
     * 购物车子项信息
     */
    List<CartItemVo> items;

    /**
     * 购物车商品总数
     */
    private Integer countNum;

    /**
     * 商品类型数量,有几种商品
     */
    private Integer countType;

    /**
     * 结算价格 = 选中的商品价格 - 优惠价格
     */
    private BigDecimal totalAmount;

    /**
     * 减免价格
     */
    private BigDecimal reduce = new BigDecimal("0.00");;



    public List<CartItemVo> getItems() {
        return items;
    }

    public void setItems(List<CartItemVo> items) {
        this.items = items;
    }

    public Integer getCountNum() {
        int count = 0;
        if (items != null && items.size() > 0) {
            for (CartItemVo item : items) {
                count += item.getCount();
            }
        }
        return count;
    }

    public Integer getCountType() {
        int count = 0;
        if (items != null && items.size() > 0) {
            for (CartItemVo item : items) {
                count += 1;
            }
        }
        return count;
    }


    public BigDecimal getTotalAmount() {
        BigDecimal amount = new BigDecimal("0");
        // 计算购物项总价
        if (!CollectionUtils.isEmpty(items)) {
            for (CartItemVo cartItem : items) {
                if (cartItem.getCheck()) {
                    amount = amount.add(cartItem.getTotalPrice());
                }
            }
        }
        // 计算优惠后的价格
        return amount.subtract(getReduce());
    }

    public BigDecimal getReduce() {
        return reduce;
    }

    public void setReduce(BigDecimal reduce) {
        this.reduce = reduce;
    }
}

二、添加商品到购物车

购物车存储结构: Map<(redis的key)用户id, Map<商品skuId, 商品信息>>
1.通过ThreadLocal获取cookie中保存的用户信息,判断用户是否登陆,分别配置redis的key(boundHashOps)
2.如果购物车中已经存在该商品,只需要修改商品件数,不需要重新添加
3.feign获取sku商品信息、属性列表 ,使用CompletableFuture异步编排提升效率

为什么线程池不能放在common中?
为什么使用同一个线程池?
如果不添加自定义线程池会怎么样? 如果不指定线程池,默认使用 ForkJoinPool,是守护线程,主线程结束,线程池关闭,自定义线程池的需要手动关闭,
为什么通过调用get方法来完成操作,CompletableFuture.anyOf(skuRunAsync,skuAttrRunAsync).get(); ? 等待所有线程执行结束
为什么自定义线程后,调用线程却是调用 ThreadPoolExecutor ?
user-key是什么时候生成的?

此方法是将商品数据存入redis,这里选用redis是因为添加购物车操作会频繁的交互数据库,但是redis的持久性有没有数据库好,所有需要增强redis的持久性,redis存数据会使用序列化操作转成字符流进行保存,我们查看数据和获取都不方便,这里直接转换为JSON格式进行存储

/**
     * 加入购物车
     */
    @Override
    public CartItemVo addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {

        //绑定key值,不用要一直set key值,使用hash存储方便查询数据

        CartItemVo cartItemVo = new CartItemVo();
        BoundHashOperations<String, Object, Object> boundHashOperations = extracted();


        /**
         * 1.如果redis中已经保存了该商品,则只需要添加件数
         */
        String res = (String) boundHashOperations.get(Long.toString(skuId));
        if (!StringUtils.isEmpty(res)){
            CartItemVo itemVo = JSON.parseObject(res, CartItemVo.class);
            itemVo.setCount(itemVo.getCount() + num);
            String jsonString = JSON.toJSONString(itemVo);
            //这里是直接覆盖?
            boundHashOperations.put(Long.toString(skuId),jsonString);
            return itemVo;
        }

        /**
         * 2.如果redis中未保存该商品
         */
        //查询sku商品信息
        CompletableFuture<Void> skuRunAsync = CompletableFuture.runAsync(new Runnable() {
            @Override
            public void run() {
                R<SkuInfoVo> info = productFeignService.info(skuId);
                if (info.getCode() == 0) {
                    SkuInfoVo skuInfoVo = info.getData(new TypeReference<SkuInfoVo>() {
                    });
                    cartItemVo.setSkuId(skuId);
                    cartItemVo.setCheck(true);
                    cartItemVo.setTitle(skuInfoVo.getSkuTitle());
                    cartItemVo.setImage(skuInfoVo.getSkuDefaultImg());
                    cartItemVo.setPrice(skuInfoVo.getPrice());
                    cartItemVo.setCount(num);
                }
            }
        }, executor);

        //查询sku属性列表
        CompletableFuture<Void> skuAttrRunAsync = CompletableFuture.runAsync(new Runnable() {
            @Override
            public void run() {
                R<List<String>> skuAttr = productFeignService.getSkuAttr(skuId);
                if (skuAttr.getCode() == 0) {
                    List<String> skuAttrData = skuAttr.getData(new TypeReference<List<String>>() {
                    });
                    cartItemVo.setSkuAttrValues(skuAttrData);
                }
            }
        }, executor);

        //等待所有的异步任务全部完成
        CompletableFuture.allOf(skuRunAsync,skuAttrRunAsync).get();

        /**
         * redis存数据会使用序列号操作转成字符流进行保存,我们查询数据和获取都不方便,这里直接转换为JSON格式进行存储
         */
        //存入redis
        String cartJson = JSON.toJSONString(cartItemVo);
        boundHashOperations.put(Long.toString(skuId),cartJson);

        System.out.println();
        return cartItemVo;
    }

封装redis的prefix前缀

 /**
     * 封装redis的prefix前缀
     */
    private BoundHashOperations<String, Object, Object> extracted() {
        UserInfoTo userInfoTo = CartIntercept.toThreadLocal.get();
        String redisKey = "";
        //判断用户是否登陆->封装前缀
        if (userInfoTo.getUserId() != null){
            //gulimall:cart:1
            redisKey = CartConstant.CART_PREFIX + userInfoTo.getUserId();
        }else {
            redisKey = CartConstant.CART_PREFIX + userInfoTo.getUserKey();
        }
        //绑定key值
        BoundHashOperations<String, Object, Object> boundHashOperations = redisTemplate.boundHashOps(redisKey);
        return boundHashOperations;
    }

三、合并购物车(离线购物车与在线购物车合并)

 (1)已登录
     1. 获取离线购物车数据
          如果有离线购物数据,有就取出数据,将离线购物数据与已登录购物数据合并,直接将离线数据添加到已登录的redis中,删除离线的redis
	            (1)如果离线购物车没数据,则只需要返回在线购物车的数据
	            (2)如果离线购物车有数据就合并数据
	            上面两种判断都涉及到离线购物车的数据,所以应该先做离线操作,
	            并且我们只需要返回在线购物车的数据,所以获取在线购物车的操作更因为放在最后执行
	            总结:以后再碰见 if(xx == null){}else(xx != null){} 的操作应该直接走 if(xx != null){}
	 2.获取已登录购物车数据,返回
 (2)离线
      获取离线购物车数据,返回
@Override
    public Cart getCart() throws ExecutionException, InterruptedException {
        Cart cart = new Cart();
        UserInfoTo userInfoTo = CartIntercept.toThreadLocal.get();
        //在线购物车(已登录用户的redis的key)
        String onlineKey = CartConstant.CART_PREFIX + userInfoTo.getUserId();
        //离线购物车(临时用户的redis的key)
        String offlineKey = CartConstant.CART_PREFIX + userInfoTo.getUserKey();
        /**
         * 已登录
         */
        if (userInfoTo.getUserId() != null){

            /**
             * 这里是要做两次判断的
             * (1)如果离线购物车没数据,则只需要返回在线购物车的数据
             * (2)如果离线购物车有数据就合并数据
             *  上面两种判断都涉及到离线购物车的数据,所以应该先做离线操作,
             *  并且我们只需要返回在线购物车的数据,所以获取在线购物车的操作更因为放在最后执行
             *  总结:以后再碰见 if(xx == null){}else(xx != null){} 的操作应该直接走 if(xx != null){}
             */
            //判断离线是否有购物数据
            List<CartItemVo> offlineVos = getCartFromRedis(offlineKey);
            if (offlineVos != null){
                //如果离线购物车有数据,就合并数据->将离线数据添加到已登录的数据中
                for (CartItemVo offlineVo : offlineVos) {
                    /**
                     * 因为上面已经判断了用户已经登录,证明UserInfo的userId是存在的
                     * addToCart()也会通过extracted()得到已登录的redisKey
                     * 所有这里执行添加购物车的操作会直接添加到已登录的购物车中
                     */
                    addToCart(offlineVo.getSkuId(),offlineVo.getCount());
                    //删除离线数据
                    redisTemplate.delete(offlineKey);
                }
            }
            //再次查询在线购物车的数据,并返回
            cart.setItems(getCartFromRedis(onlineKey));
        }else {
            /**
             * 离线
             */
            cart.setItems(getCartFromRedis(offlineKey));
        }
        return cart;
    }

根据key获取redis中存的值

 /**
     * 根据key获取redis中存的值
     */
    @Override
    public List<CartItemVo> getCartFromRedis(String redisKey) {
        BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(redisKey);
        List<Object> values = ops.values();
        if (values != null && values.size() > 0){
            List<CartItemVo> collect = values.stream().map(obj -> {
                //因为我们存购物数据是使用的JSON格式,所有取数据时直接json字符串转java对象
                String str = (String) obj;
                CartItemVo itemVo = JSON.parseObject(str, CartItemVo.class);
                return itemVo;
            }).collect(Collectors.toList());
            return collect;
        }
        return null;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/700254.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis6之哨兵模式

哨兵&#xff1a;是用于监控Redis集群中主节点状态的工具&#xff0c;是Redis高可用解决方案&#xff0c;哨兵可以监视一个或者多个redis 主节点&#xff0c;以及这些主节点的所有从节点。 某个主节点服务宕机后&#xff0c;会把这个主节点下的某个从节点升级为主节点来替代已宕…

软件测试技能,JMeter压力测试教程,监听器之灵活的文件写入(十六)

一、前言 JMeter使用Simple Data Writer插件具有非常简单的测试结果编写功能。文件格式仅限于XML和CSV&#xff0c;字段顺序已预定义。因此&#xff0c;你可能会发现Simple Data Writer不足以完成您的任务 Flexible File Writer插件允许以灵活的格式编写测试结果&#xff0c;…

HarmonyOS 4.0开发者Beta招募,快来报名尝鲜

作为一枚资深玩机党&#xff0c;相比同质化日趋严重的硬件配置&#xff0c;我对各大手机厂商的系统软件更感兴趣。把玩了几年iPhone、三星及国产安卓手机&#xff0c;沉浸体验了iOS、Android几番更新迭代&#xff0c;暮然回首——还是咱们“土生土长”的HarmonyOS更贴合我个人的…

如何使用Claude(超详细教程)

如何使用Claude(超详细教程) 1. 目前claude只支持在slack中使用 点击进入claude官网然后点击添加到Slack中 2. 跳转到Slack创建工作区 点击创建新的工作区&#xff08;以前使用过Slack可用以前的工作区&#xff09; 输入邮箱&#xff0c;建议谷歌邮箱登录&#xff08;其他邮…

ValSuite报告可以帮助改善您的验证过程的6种方式

热验证工艺是一项复杂而微妙的工作&#xff0c;但它是确保制药和生物技术产品的安全性和有效性的重要组成部分。同时&#xff0c;管理整个验证过程中产生的数据可能很费时&#xff0c;而且容易出错——这就是ValSuite的意义。 这款直观的验证软件简化了数据分析和报告&#xf…

Visual C++中*号位置和功能详细解说

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天来聊聊Visual C中*号的位置。 我知道在程序员队伍中有一群特别细心、谨慎的可爱的人&#xff0c;他们经常为一些在别人看来小的不能再小的问题所困惑。比如说&#xff0c;*号的位置&#xff0c;让很多人…

SpringBoot集成MinIO

简介 对象存储服务OSS&#xff08;Object Storage Service&#xff09;是一种海量、安全、低成本、高可靠的云存储服务&#xff0c;适合存放任意类型的文件。容量和处理能力弹性扩展&#xff0c;多种存储类型供选择&#xff0c;全面优化存储成本。今天我这里主要讲解SpringBoo…

工业物联网解决方案:水轮机PLC数据采集远程监控系统

水轮机是一种能将水的动能转化为旋转机械能的动力设备&#xff0c;是水电站的重要工业设备。随着工业自动化技术与PLC设备的推广应用&#xff0c;水轮机可以通过PLC实现自动化控制&#xff0c;进而通过搭建PLC数据采集远程监控系统&#xff0c;实现水轮机的实时在线监控&#x…

产品再次跳票 法拉第未来的融资价值却渐渐浮现?

从“生态化反”到“极智科技顶奢”&#xff0c;贾跃亭讲故事的出众能力&#xff0c;总是能为旗下企业和产品赚足市场眼球。 但仅有宏大的前景描绘&#xff0c;似乎难以触动理性的资本市场&#xff0c;其创立的新能源车企法拉第未来&#xff08;NASDAQ&#xff1a;FFIE&#xf…

在IDEA中将自定义的配置文件设置到SpringBoot工程中

可以帮助我们生成自定义配置的元数据信息&#xff0c;让配置文件和Java代码之间的对应参数可以自动定位&#xff0c;方便开发。 <!-- 生成自定义配置的元数据信息 --><dependency><groupId>org.springframework.boot</groupId><artifactId>sprin…

Hiera:一个没有Bells-and-Whistles的分层视觉转换器

文章目录 Hiera: A Hierarchical Vision Transformer without the Bells-and-Whistles摘要本文方法实验结果 Hiera: A Hierarchical Vision Transformer without the Bells-and-Whistles 摘要 现代层次视觉转换器在追求监督分类性能的过程中增加了一些特定于视觉的组件。虽然…

PS修改日期(简单两步)

PS修改日期&#xff08;简单两步&#xff09; Q&#xff1a;将图中的日期修改为6月10日 选择画笔工具&#xff0c;把要修改的日期涂抹掉 设置前景色&#xff0c;使用吸管吸附要涂抹的颜色&#xff0c;然后点击确定 使用设置好颜色的画笔&#xff0c;在要修改的日期上涂抹 …

接口自动化测试实操【设置断言思路】

1 断言设置思路 这里总结了我在项目中常用的5种断言方式&#xff0c;基本可能满足90%以上的断言场景&#xff0c;具体参见如下脑图&#xff1a; 在这里插入图片描述 下面分别解释一下图中的五种思路&#xff1a; 1&#xff09; 响应码 对于http类接口&#xff0c;有时开发人…

期末复习【计算机网络】

期末复习【计算机网络】 前言推荐期末复习如何快速阅读电子书重点第1章 概述1.6 计算机网络的性能1.6.2 计算机网络的性能指标√ 1.7 计算机网络体系结构1.7.3 具有五层协议的体系结构√ 第2章 物理层2.3 物理层下面的传输媒体 *2.4 信道复用技术2.4.1 频分复用、时分复用和统计…

天津大学天津市认知计算与应用重点实验室视听觉认知计算团队12篇论文被语音处理顶会Interspeech 2023接收

天津大学天津市认知计算与应用重点实验室视听觉认知计算团队12篇论文被语音技术顶会Interspeech 2023接收&#xff0c;涵盖意图识别、口语理解、声学特征、语音识别、语音分离、情感识别等研究方向&#xff0c;论文简介如下。 01. Rethinking the visual cues in audio-visual…

Harbor 间通过API实现镜像复制

背景&#xff1a; 通过harbor搭建私有镜像仓库时&#xff0c;往往会设计出 研发镜像仓库--》测试镜像仓库--》产品镜像仓库&#xff0c;甚至更多。那个多镜像间的复制将成了一个必须解决的问题。当然可以通过harbor自带的界面操作即可实现需求&#xff0c;也可以通过harbor提供…

详解20个常见的模拟电路

桥式整流电路 二极管的单向导电性&#xff1a;二极管的PN结加正向电压&#xff0c;处于导通状态&#xff1b;加反向电压&#xff0c;处于截止状态。其伏安特性曲线&#xff0c;如下图。 理想开关模型和恒压降模型&#xff1a;理想模型指的是在二极管正向偏置时&#xff0c;其管…

乳腺癌预测的黑科技!一起探索 R 语言和随机森林算法的奇妙之处!

一、引言 乳腺癌是全球最常见的女性恶性肿瘤之一&#xff0c;也是导致女性死亡的主要原因之一。根据世界卫生组织的数据&#xff0c;每年有约200万例乳腺癌新病例的发生&#xff0c;导致60多万人死亡。乳腺癌的早期发现和精准治疗对于患者的生存率和预后至关重要。 然而&…

uniapp封装门票等票务样式

先看效果图 ticketpage组件引用后&#xff0c;根据父级背景颜色改变镂空的颜色,空组件只有中间镂空的样式&#xff0c;上面是插槽heaer,下面内容是插槽content&#xff0c;可以自定义自己的内容和样式。我实现的最终效果是用的uview组件&#xff0c;如果复现需要项目引入。可…

pytorch量化库使用(2)

FX Graph Mode量化模式 训练后量化有多种量化类型&#xff08;仅权重、动态和静态&#xff09;&#xff0c;配置通过qconfig_mapping &#xff08; prepare_fx函数的参数&#xff09;完成。 FXPTQ API 示例&#xff1a; import torch from torch.ao.quantization import (ge…