• mmall商城购物车模块总结


    购物车模块的设计思想

    购物车的实现方式有很多,但是最常见的就三种:Cookie,Session,数据库.三种方法各有优劣,适合的场景各不相同.Cookie方法:通过把购物车中的商品数据写入Cookie中,再通过浏览器进行读取.这个方法,适合在用户没有登录的情况下使用,但是有个非常严重的缺点,即在用户禁用了Cookie的时候是无法使用的.Session方法:通过Session来保存商品信息,这确实是个好的方法,适合用户已经登录的情况,将数据放在Session中,用户就能读取购物车中的商品信息,而且速度十分的快.但是缺点也很明显,由于Session是建立在用户客户端和服务器之间的,在Session中保存数据,无疑会增加服务器的负担.数据库(Redis):数据库无疑是一种非常棒的保存购物车中信息的有效途径,且能够持久化保存,但是问题也很明显,那就是读取速度会差强人意.

    封装一个高复用购物车核心方法

    这个模块中封装了两个value object类;一个是CartProductVo,另一个是CartVo,这两个有什么联系呢?
    首先我们回想一下京东或者淘宝的购物车,这个购物车显示给我的肯定含有商品的一些属性,例如名称,单价,主图,标题等,除了这些还有他自己的一些属性,像总的价格,目前是否勾选,商品的状态等等;这就是购物车与商品结合的VO,CartProductVo,而CartVo是对cartProductVo的大的包装,我们最后在渲染视图的时候,返回的对象就是CartVo;
    CartProductVo类

    public class CartProductVo {
        //结合Cart和product封装一个抽象对象
        private Integer id;
        private Integer userId;
        private Integer productId;
        private Integer quantity; //数量
        private String productName;
        private String productSubTitle;
        private String productMainImage;
        private BigDecimal productPrice;
        private Integer status;
        private BigDecimal productTotalPrice;
        private Integer productStock;   //库存
        private Integer productChecked;  //是否勾选
        private String limitQuantity;//限制数量的返回结果
    

    CartVo类

    public class CartVo {
        private List<CartProductVo> cartProductVoList;
        private BigDecimal cartTotalPrice;
        private Boolean allChecked;    //全选
        private String imageHost;//图片路径
    

    在购物车操作的时候,用户对购物车进行一列的添加,删除,修改数量,而我们要显示最终的CartVo对象给他;知道要干什么之后,就是想实现方法了,我们可以获取这个用户id,从而知道用户以后的操作;我们根据用户id查找到这个用户的购物车,返回是一个集合,集合中的元素是购物车,然后判断一下这个集合是否为空且它的size是不是为0,如果不是,开始遍历这个集合,我们先把pojo中的Cart封装到CartProductVo对象中,再把CartProduct对象装到CartVo的list集合中一层一层的装载,最终返回我们想要的结果。当然中间因为含有产品的一些属性,所以我们要再次从数据库中找到这个商品,返回到service,从而将属性赋值给CartProductVo对象。在这个过程中,我们要注意的是,判断用户当前购买某个商品的数量是否超过库存量,如果超过,就不能再增加,库存量要更新值为当前数量,并且反回失败。计算这个商品的总金额就是数量x单价;调用BigDecimalUtil的乘法运算。
    具体实现代码如下:

     private CartVo getCartVoLimit(Integer userId) {
            List<Cart> cartList = cartMapper.selectCartByUserId(userId);
            CartVo cartVo = new CartVo();
            List<CartProductVo> cartProductVoList = Lists.newArrayList();
            //处理计算精度缺失
            BigDecimal totalPrice = new BigDecimal("0");
            if (CollectionUtils.isNotEmpty(cartList)) {
                for (Cart cart : cartList) {
                    CartProductVo cartProductVo = new CartProductVo();
                    cartProductVo.setId(cart.getId());
                    cartProductVo.setUserId(userId);
                    cartProductVo.setProductId(cart.getProductId());
                    Product product = productMapper.selectByPrimaryKey(cart.getProductId());
                    if (product != null) {
                        cartProductVo.setProductMainImage(product.getMainImage());
                        cartProductVo.setProductName(product.getName());
                        cartProductVo.setProductPrice(product.getPrice());
                        cartProductVo.setProductSubTitle(product.getSubtitle());
                        cartProductVo.setProductStock(product.getStock());
                        cartProductVo.setStatus(product.getStatus());
                        //判断库存
                        int buyLimitCount = 0;
                        if (product.getStock() >= cart.getQuantity()) {
                            //如果购物车中的某件商品数量小于等于产品的总数,库存充足
                            buyLimitCount = cart.getQuantity();
                            cartProductVo.setLimitQuantity(Const.Cart.LIMIT_NUM_SUCCESS);
                        } else {
                            //如果买的数量大于库存总数,更新库存,并返回失败
                            buyLimitCount = product.getStock();
                            cartProductVo.setLimitQuantity(Const.Cart.LIMIT_NUM_FAIL);
                            //更新有效库存
                            Cart cart1 = new Cart();
                            cart1.setId(cart.getId());
                            cart1.setQuantity(buyLimitCount);
                            cartMapper.updateByPrimaryKeySelective(cart);
                        }
                        cartProductVo.setQuantity(buyLimitCount);
                        //计算总价格,单价 X 数量
                        double num = cartProductVo.getQuantity();
                        double unitPrice = product.getPrice().doubleValue();
                        cartProductVo.setProductTotalPrice(BigDecimalUtil.mul(unitPrice, num));
                        cartProductVo.setProductChecked(cart.getChecked());
    
                    }
                    //如果有一项被勾选就加入到总价中
                    if (cart.getChecked() == Const.Cart.CHECKED) {
                        //加他自己
                        totalPrice = BigDecimalUtil.add(cartProductVo.getProductTotalPrice().doubleValue(), totalPrice.doubleValue());
                    }
                    //最后在把封装好的vo添加到list集合
                    cartProductVoList.add(cartProductVo);
                }
            }
            cartVo.setAllChecked(this.getCheckedStatus(userId));
            cartVo.setCartProductVoList(cartProductVoList);
            cartVo.setCartTotalPrice(totalPrice);
            cartVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix"));
            return cartVo;
        }
    

    解决浮点型商业运算中丢失精度的问题

    因为对于java来讲并没有提供支付时所处理的元素方法,当我们直接使用浮点类型进行加减乘除运算的时候会出现以下情况

     public void test1(){
            System.out.println(1.2+3.15);//猜测是4.35
            System.out.println(2.9-1.12);//猜测是1.78
            System.out.println(1.3*3);//猜测是3.9
            System.out.println(36.6/3.0);//猜测是412.2
        }
        但是实际结果是
        4.35
        1.7799999999999998
        3.9000000000000004
        12.200000000000001
    

    因此如果直接使用浮点类型进行商品交易结算时这时会出大问题的;所以我们需要封装一个专门处理商业运算的工具类;选取BigDecimal类,使用它的含有字符串参数的构造方法创建对象是安全可靠的。

    package cn.edu.mmall.util;
    
    import java.math.BigDecimal;
    
    public class BigDecimalUtil {
    
        private BigDecimalUtil() {
        }
        /**
         *加法
         * @param v1
         * @param v2
         * @return
         */
        public static BigDecimal add(double v1,double v2){
            BigDecimal b1 = new BigDecimal(Double.toString(v1));
            BigDecimal b2 = new BigDecimal(Double.toString(v2));
            return b1.add(b2);
        }
    
        /**
         * 减法
         * @param v1
         * @param v2
         * @return
         */
        public static BigDecimal sub(double v1,double v2){
            BigDecimal b1 = new BigDecimal(Double.toString(v1));
            BigDecimal b2 = new BigDecimal(Double.toString(v2));
            return b1.subtract(b2);
        }
    
        /**
         * 乘法
         * @param v1
         * @param v2
         * @return
         */
        public static BigDecimal mul(double v1,double v2){
            BigDecimal b1 = new BigDecimal(Double.toString(v1));
            BigDecimal b2 = new BigDecimal(Double.toString(v2));
            return b1.multiply(b2);
        }
    
        /**
         * 两数相除,需要保留小数点后两位,四舍五入
         * @param v1
         * @param v2
         * @return
         */
        public static BigDecimal div(double v1,double v2){
            BigDecimal b1 = new BigDecimal(Double.toString(v1));
            BigDecimal b2 = new BigDecimal(Double.toString(v2));
            //四舍五入保留两位小数
            return b1.divide(b2,2,BigDecimal.ROUND_HALF_UP);
        }
    }
    
    

    BigDecimal类中还根据对应的参数设置了小数的处理方式,四舍五入,向上或向下取整,保留小数位等等,功能非常强大,但是重要的是,我们创建对象的时候一定要使用字符串参数的构造函数,只有这个才能确保数据计算过程中不会出现精度丢失。

    功能的介绍

    加入商品;更新商品、查询商品数、移除商品、单选/取消,全选/取消,购物车列表

    加入商品:
    如果商品id和数量又一个为空返回参数错误;然后根据用户id和商品id查询出这个购物车,如果购物ce为null,说明这个商品是第一加入,那就新建一个Cart对象,设置id,数量,选中,以及用户id,插入数据库即可,否则就是含有该商品,其余不动,商品的数量修改为传入的数据。

     public ServerResponse<CartVo> add(Integer userId, Integer productId, Integer count) {
            if (productId == null || count == null) {
                return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(),
                        ResponseCode.ILLEGAL_ARGUMENT.getDesc());
            }
    
            Cart cart = cartMapper.selectByUserIdAndProductId(userId, productId);
            if (cart == null) {
                //这个产品不在这个购物车里,需要新增加一个产品记录
                Cart cartItem = new Cart();
                cartItem.setProductId(productId);
                cartItem.setQuantity(count);
                cartItem.setChecked(Const.Cart.CHECKED);
                cartItem.setUserId(userId);
                cartMapper.insert(cartItem);
            } else {
                //如果含有这个商品,数量加count
                count = cart.getQuantity() + count;
                cart.setQuantity(count);
                cartMapper.updateByPrimaryKeySelective(cart);
            }
            return list(userId);
        }
    

    list方法:

    public ServerResponse<CartVo> list(Integer userId) {
            CartVo cartVoLimit = this.getCartVoLimit(userId);
            return ServerResponse.createBySuccessData(cartVoLimit);
        }
    

    我们需要判断用户是不是全选所有商品,这时候的SQL语句就有技巧了,我们可以反着来处理,查询所有未选中的商品数量如果是0,就返回true,否则返回false;

    private boolean getCheckedStatus(Integer userId) {
            if (userId == null) {
                return false;
            }
            //开始从数据库中查询当前用户购物车是否全选,全选返回true,否则false;
            int resultCount = cartMapper.selectCartProductCheckStatusByUserId(userId);
            return resultCount == 0;
        }
    

    剩余代码:

     public ServerResponse<CartVo> update(Integer userId, Integer productId, Integer count) {
            if (productId == null || count == null) {
                return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(),
                        ResponseCode.ILLEGAL_ARGUMENT.getDesc());
            }
    
            Cart cart = cartMapper.selectByUserIdAndProductId(userId, productId);
            if (cart == null) {
                cart.setQuantity(count);
            }
            cartMapper.updateByPrimaryKeySelective(cart);
            return list(userId);
        }
    
        @Override
        public ServerResponse<CartVo> delete(Integer userId, String productIds) {
            List<String> productList = Splitter.on(",").splitToList(productIds);
            if (CollectionUtils.isEmpty(productList)) {
                return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(),
                        ResponseCode.ILLEGAL_ARGUMENT.getDesc());
            }
            int i = cartMapper.deleteByUserIdAndProducts(userId, productList);
            return list(userId);
        }
    

    根据用户id获取全购物车的商品数量

     <select id="selectCartProductCount" resultType="java.lang.Integer" parameterType="integer">
            select IFNULL(sum(quantity),0) as count from mmall_cart where user_id =#{userId}
        </select>
    

    处理全选,全不选时候,商品id传null;单选,取消单选操作的时候,传的值不为null;
    四种不同的请求对应一个处理方法,dao中是一条SQL语句;

      <update id="checkedOrUnCheckedProduct" parameterType="map">
            update mmall_cart
             set checked = #{checked},
             update_time=now()
            where user_id =#{userId}
            <if test="productId !=null">
                and product_id=#{productId}
            </if>
        </update>
    
    public ServerResponse<CartVo> selectOrUnSelect(Integer userId, Integer productId, Integer checkedId) {
            cartMapper.checkedOrUnCheckedProduct(userId, productId, checkedId);
            return list(userId);
        }
    
  • 相关阅读:
    kendo ui 查找treelist里的子控件并设置是否显示的方法
    KendoUi下的DatePicker在谷歌浏览器上不能正常显示时间的解决方法
    asp.net 第三方UI控件 Telerik KendoUI 之 TreeVIew 的用法记录
    mysql批量更新数据,即:循环select记录然后更新某一字段
    Sql语句备份Sqlserver数据库
    3des用法示例,已测试
    制作Windows服务项目详细攻略
    利用好压在C#程序里实现RAR格式的压缩和解压功能
    winform里textBox无法获得焦点的解决方案
    Shell脚本批量重命名案例
  • 原文地址:https://www.cnblogs.com/itjiangpo/p/14181417.html
Copyright © 2020-2023  润新知