购物车原理以及实现

本文讲什么

可以看到,购物车这样一个功能模块,在各种购物类APP或者web应用中绝对是必不可少的东西.不论在大学中的课程设计,还是在实际的项目开发中,绝对非常重要且有些复杂的内容.
在实际操作中,身边有很多的小伙伴遇到编写购物车的代码的时候,有时候真的是一脸懵逼,总是搞不明白设计的思路,这就是本文写作的原因.
所以,本文适合搞不清楚购物车实现原理,知道原理但是实际编码不知道如何下手的小伙伴,我将给出一个思路以及实际的代码供大家参考.
在本文中,我将会用尽可能简单的句子,表达出我想表达的意思.废话不多说,开始我们的购物车实战!

购物车的几种实现方式

购物车的实现方式有很多,但是最常见的就三种:Cookie,Session,数据库.三种方法各有优劣,适合的场景各不相同.

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

好了,下面来说一下几种实现方式的应用场景.

  • 当用户没有登录的情况下,用户将商品加入购物车,此时的商品信息是写入了Cookie中,并且会设置一个保存时间,即使关闭浏览器过一段时间访问仍能看到购物车中的信息.(或者将购物数据写入Session中,但是关闭浏览器,购物车中的信息也就不见了)
  • 用户登陆后,如果在Session中存储了商品信息且没有关闭浏览器(如果在Cookie中存储了商品信息且没有过期),将会读取其中的商品信息,并且将这些信息写入数据库中进行持久保存.

本文的行文方式说明

经过上面的讲解,我想你一定对购物车有所了解,为了使读者更加清晰的明白购物车的实现,我们省去了在未结算的状态下的持久化数据库.
也就是说,在文章中,我将使用Session来实现购物车,并且当用户没有登录的情况下,禁止用户将商品加入购物车.当然你不必为此担忧,即使我这样做,我的代码已经包括了整个购物操作的绝大多数步骤.请耐心向下看.
此外,本文使用SSM框架作为行文代码.
如果你是初学者也不必担心,我将为你提供一套项目的源代码,可以在我的GitHub中获取—->餐厅点餐系统,这套系统是基于servlet+jsp+mysql开发的,注释非常完善,当然最重要的模块也就是下单模块肯定是有的,而且非常完善,欢迎下载.

购物车模块的实现

数据库设计

  • 用户表
    字段 意义
    id 用户id
    userName 用户名
    password 用户密码
  • 商品表

    字段 意义
    id 商品id
    commName 商品名称
    price 商品价格
  • 订单表

    字段 意义
    id 订单id
    commName 商品名称
    count 商品数量
    subtotal 商品小计
    total 总价

用户登录

为了实现我上述的思路,肯定是要求用户先行登录.代码如下.

  • LoginController
@Controller
public class LoginController {
    private LoginService loginService;
    private CommonService commonService;

    @Autowired
    public LoginController(LoginService loginService, CommonService commonService) {
        this.loginService = loginService;
        this.commonService = commonService;
    }

    @RequestMapping("/login")
    public String login(User user, HttpSession session, Model model) {
        //登录验证
        if (loginService.isUser(user)) {
            List<Common> commons = commonService.selectAllCommons();
            model.addAttribute("commons", commons);
            model.addAttribute("username", user.getUsername());
            //把用户信息保存到session中
            session.setAttribute("user", user);
            return "shopping";
        } else {
            model.addAttribute("message", "用户名或密码错误");
            return "index";
        }
    }
}

这是最常规的用户登录的代码,思路就是拿着用户名到数据库里面查询,如果能查到,则说明用户名无误,如果查不到则说明没有此用户,提示用户注册.如果用户名存在,再比对密码,一般密码不会像我们这样直接在数据库里面使用明文密码,都是会加盐的(MD5算法).如果比对密码的结果为true,则用户可以登录.比对过程isUser的代码如下.

@Service
public class LoginService {
    private UserMapper userMapper;

    @Autowired
    public LoginService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    /**
     * 判断用户名或密码是否正确
     * @param user
     * @return
     */
    public boolean isUser(User user){
        UserExample example = new UserExample();
        UserExample.Criteria criteria  = example.createCriteria();
        criteria.andUsernameEqualTo(user.getUsername());
        List<User> eqUser = userMapper.selectByExample(example);
        //如果没有查询到user,则返回false
        if (eqUser == null){
            return false;
        }
        return eqUser.get(0).getPassword().equals(user.getPassword());
    }

}

确认用户登录以后,需要把用户信息放在session中以便后续过程使用.

用户购物模块

当用户登录以后,展示在用户眼前的界面是这样的(页面模板来自菜鸟教程,经过改编):

左栏中的数据来自登录代码中的

List<Common> commons = commonService.selectAllCommons();
model.addAttribute("commons", commons);

当点击加入购物车以后,触发onlick事件:onclick="javascript:joinCart(${common.id})"
详细代码如下:

function joinCart(id) {
        $.ajax({
            url: "${pageContext.request.contextPath}/shop/joinCart",
            data: "id=" + id,
            type: "POST",
            success: function (result) {
                alert("加入购物车成功!");
                //清空购物车列表
                $("#shop_cart tbody").empty();
                //动态构建购物车列表
                var obj = result;
                $.each(obj,function (index,item) {
                   var emptyTd = $("<td></td>").append("#");
                   var commnameTd = $("<td></td>").append(item.commname);
                   var countTd = $("<td></td>").append(item.count);
                   var subtotalTd = $("<td></td>").append(item.subtotal);

                   $("<tr></tr>")
                       .append(emptyTd)
                       .append(commnameTd)
                       .append(countTd)
                       .append(subtotalTd)
                       .appendTo("#shop_cart tbody");
                   //设置总金额
                   var totalSpan = document.getElementById("subtotal");
                   totalSpan.innerHTML = item.total;
                });


            }
        })
    }

可以看到,当触发这个方法时,实际上是使用了异步请求的方式向服务端发送数据,服务端做相应处理以后,封装购物车列表,然后把购物车商品列表以JSON格式传回,也就是封装在result中,利用js,动态构建购物车列表.于是就出现下面这种情况.
当将商品加入购物车以后:

首先提示用户已经加入购物车,然后在利用异步请求构建整个购物车,如果你对前端的了解并不是很深,不必担心,这部分内容实际上很简单,你可以随便百度一下这个知识点,记住就好了.实际上就是利用js操作json数据而已.

其实对于初学者来说,感觉上面的过程挺神奇的,其实不然.前端的代码看完了,我就来给你详细的解释一下后端的代码.
首先,可以将后段代码分成两部分,一部分是判断,各种判断,另外一部分是封装数据,相对简单.

 //标识符:判断是否存在此商品
boolean flag = false;
//通过id查询商品信息
Common common = shopService.selectCommById(Integer.parseInt(id));
//从session中获取购物车信息
List<Order> shopcart = (List<Order>) session.getAttribute("shopcart");
//获取用户信息
User user = (User) session.getAttribute("user");
if (user == null) {
    //如果用户为空,则直接返回,让用户登录
    throw new RuntimeException("用户未登录");
}
//判断购物车列表是否为空
if (shopcart == null) {
    //new 一个集合
    shopcart = new ArrayList<>();
}

先判断用户是否已经登录,如果用户已经登录,则执行后续流程,如果用户没有登录,则直接抛出异常,交给异步请求的error处理,提示用户登录即可.
如果用户已经登录,则继续下面的步骤,判断购物车是否为空,为空也就是说明用户没有将任何商品加入购物车,则需要创建一个新的ArrayList集合,同时利用商品id查询出的商品信息,封装订单对象.

else {
    for (Order order : shopcart) {
        //判断是否存在此商品,存在则数量+1
        if (order.getCommname().equals(common.getCommname())) {
            flag = true;
            order.setCount(order.getCount() + 1);
            order.setSubtotal(order.getCount() * common.getPrice());
        }
    }
}

在遍历的过程中,如果遍历的数据的commName和查到的name相同的话,则证明是同一件商品,则将此商品的数量+1,然后再设置小计价格即可.同时将flag设置为true,表示遍历到了同样的数据.
如果没有遍历到名称相同的商品,则直接新建一个对象,封装数据,加入集合.

//如果购物车中没有当前商品的信息,则新增商品
if (!flag) {
    Order order = new Order();
    order.setCommname(common.getCommname());
    order.setCount(1);
    order.setSubtotal(common.getPrice());
    //把商品加入集合
    shopcart.add(order);
}

最后一步就是计算总价格,封装数据即可.

//计算总价格
Double total = 0d;
for (Order order : shopcart) {
    total += order.getSubtotal();
}
for (Order order : shopcart) {
    order.setTotal(total);
}
//设置session
session.setAttribute("shopcart", shopcart);
//返回list
return shopcart;

购物车全部代码如下:

Controller
@RequestMapping("/shop")
public class ShopController {
    private ShopService shopService;

    public ShopController(ShopService shopService) {
        this.shopService = shopService;
    }

    @RequestMapping("/joinCart")
    @ResponseBody
    public List<Order> joinCart(String id, HttpSession session, Model model) {
        //标识符:判断是否存在此商品
        boolean flag = false;
        //通过id查询商品信息
        Common common = shopService.selectCommById(Integer.parseInt(id));
        //从session中获取购物车信息
        List<Order> shopcart = (List<Order>) session.getAttribute("shopcart");
        //获取用户信息
        User user = (User) session.getAttribute("user");
        if (user == null) {
            //如果用户为空,则直接返回,让用户登录
            throw new RuntimeException("用户未登录");
        }
        //判断购物车列表是否为空
        if (shopcart == null) {
            //new 一个集合
            shopcart = new ArrayList<>();
        } else {
            for (Order order : shopcart) {
                //判断是否存在此商品,存在则数量+1
                if (order.getCommname().equals(common.getCommname())) {
                    flag = true;
                    order.setCount(order.getCount() + 1);
                    order.setSubtotal(order.getCount() * common.getPrice());
                }
            }
        }
        //如果购物车中没有当前商品的信息,则新增商品
        if (!flag) {
            Order order = new Order();
            order.setCommname(common.getCommname());
            order.setCount(1);
            order.setSubtotal(common.getPrice());
            //把商品加入集合
            shopcart.add(order);
        }
        //计算总价格
        Double total = 0d;
        for (Order order : shopcart) {
            total += order.getSubtotal();
        }
        for (Order order : shopcart) {
            order.setTotal(total);
        }
        //设置session
        session.setAttribute("shopcart", shopcart);
        //返回list
        return shopcart;
    }

}

关于操作购物车商品数量及结算

首先说操作购物车商品数量,既然我们能够按照通过id加商品的数量,肯定也是能够按照商品id减商品的数量,这部分无需多说,相信按照上面的代码,以你的聪明才智,肯定是能够做出来的.
至于结算操作,就更加单了.
用户点击结算按钮以后,跳转到后台,通过后台代码,先把session中保存的数据取出,然后遍历将数据写入数据库,进行持久化的操作即可.
以上.
文章配套源代码:https://download.csdn.net/download/yanmiao0715/10570386
如果您的积分不够,欢迎关注我的微信公众号:最高权限比特流,回复”购物车源代码”进行下载.

结语

感谢您的阅读,如果您对文章有任何问题,欢迎留言,欢迎联系我:roobtyan@outlook.com.
也欢迎您关注我的微信公众号:最高权限比特流.
以及我的个人博客:www.roobtyan.cn

发表评论

电子邮件地址不会被公开。 必填项已用*标注