秒杀的业务场景广泛存在于电商当中,即有一个倒计时的时间限制,当倒计时为0时,秒杀开始,秒杀之后持续很小的一段时间,而且秒杀的商品很少,因此会有大量的顾客进行购买,会产生很大的并发量,从而创造技术难点
本章将编写一个不涉及并发操作的秒杀逻辑实现,包括商品页面,详情页面,以及订单页面。
首先,当用户登录之后,跳转到商品页面,罗列了所有可以秒杀的商品。
@Autowired private GoodsService goodsService; @RequestMapping("/to_list") public String list(Model model,MiaoshaUser user ){ List<GoodsVo> goodsList = goodsService.listGoodsVo(); model.addAttribute("user",user); model.addAttribute("goodsList",goodsList); return "goods_list"; }
代码如上所示,这里之所以能取到user,是利用了分布式session的设计。
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>商品列表</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <!-- jquery --> <script type="text/javascript" th:src="@{/js/jquery.min.js}"></script> <!-- bootstrap --> <link rel="stylesheet" type="text/css" th:href="@{/bootstrap/css/bootstrap.min.css}" /> <script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.js}"></script> <!-- jquery-validator --> <script type="text/javascript" th:src="@{/jquery-validation/jquery.validate.min.js}"></script> <script type="text/javascript" th:src="@{/jquery-validation/localization/messages_zh.min.js}"></script> <!-- layer --> <script type="text/javascript" th:src="@{/layer/layer.js}"></script> <!-- md5.js --> <script type="text/javascript" th:src="@{/js/md5.min.js}"></script> <!-- common.js --> <script type="text/javascript" th:src="@{/js/common.js}"></script> </head> <body> <div class="panel panel-default"> <div class="panel-heading">秒杀商品列表</div> <table class="table" id="goodslist"> <tr><td>商品名称</td><td>商品图片</td><td>商品原价</td><td>秒杀价</td><td>库存数量</td><td>详情</td></tr> <tr th:each="goods,goodsStat : ${goodsList}"> <td th:text="${goods.goodsName}"></td> <td ><img th:src="@{${goods.goodsImg}}" width="100" height="100" /></td> <td th:text="${goods.goodsPrice}"></td> <td th:text="${goods.miaoshaPrice}"></td> <td th:text="${goods.stockCount}"></td> <td><a th:href="‘/goods/to_detail/‘+${goods.id}">详情</a></td> </tr> </table> </div> </body> </html>
从商品页面代码,点击详情的超链接可以到达去商品详情的控制器
@RequestMapping("/to_detail/{goodsId}") public String detail(Model model, MiaoshaUser user, @PathVariable("goodsId") long goodsId){ model.addAttribute("user",user); GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId); model.addAttribute("goods", goods); //得到秒杀的开始时间、结束时间、以及当前时间 long startAt = goods.getStartDate().getTime(); long endAt = goods.getEndDate().getTime(); long now = System.currentTimeMillis(); //设置剩余时间 int remainSeconds=0; //设置秒杀状态 int miaoshaStatus=0; //判断 if(now<startAt){ //秒杀还没开始 miaoshaStatus=0; remainSeconds= (int) ((startAt-now)/1000); }else if(now>endAt){ //秒杀已经结束 miaoshaStatus=2; remainSeconds=-1; }else { //秒杀正在进行 miaoshaStatus=1; remainSeconds=0; } model.addAttribute("miaoshaStatus",miaoshaStatus); model.addAttribute("remainSeconds",remainSeconds); return "goods_detail"; }
该方法从页面传来商品id的值,然后从数据库中取出该商品的秒杀开始时间。结束时间等,判断秒杀的状态,0为未开始,1为正在进行,2为已经结束。然后返回商品详情。剩余时间、以及秒杀的状态,然后跳转到商品的详情页面,在详情页面中,详细列出该商品的信息。
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>商品详情</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <!-- jquery --> <script type="text/javascript" th:src="@{/js/jquery.min.js}"></script> <!-- bootstrap --> <link rel="stylesheet" type="text/css" th:href="@{/bootstrap/css/bootstrap.min.css}" /> <script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.js}"></script> <!-- jquery-validator --> <script type="text/javascript" th:src="@{/jquery-validation/jquery.validate.min.js}"></script> <script type="text/javascript" th:src="@{/jquery-validation/localization/messages_zh.min.js}"></script> <!-- layer --> <script type="text/javascript" th:src="@{/layer/layer.js}"></script> <!-- md5.js --> <script type="text/javascript" th:src="@{/js/md5.min.js}"></script> <!-- common.js --> <script type="text/javascript" th:src="@{/js/common.js}"></script> </head> <body> <div class="panel panel-default"> <div class="panel-heading">秒杀商品详情</div> <div class="panel-body"> <span th:if="${user eq null}"> 您还没有登录,请登陆后再操作<br/></span> <span>没有收货地址的提示。。。</span> </div> <table class="table" id="goodslist"> <tr> <td>商品名称</td> <td colspan="3" th:text="${goods.goodsName}"></td> </tr> <tr> <td>商品图片</td> <td colspan="3"><img th:src="@{${goods.goodsImg}}" width="200" height="200" /></td> </tr> <tr> <td>秒杀开始时间</td> <td th:text="${#dates.format(goods.startDate, ‘yyyy-MM-dd HH:mm:ss‘)}"></td> <td id="miaoshaTip"> <input type="hidden" id="remainSeconds" th:value="${remainSeconds}" /> <span th:if="${miaoshaStatus eq 0}">秒杀倒计时:<span id="countDown" th:text="${remainSeconds}"></span>秒</span> <span th:if="${miaoshaStatus eq 1}">秒杀进行中</span> <span th:if="${miaoshaStatus eq 2}">秒杀已结束</span> </td> <td> <form id="miaoshaForm" method="post" action="/miaosha/do_miaosha"> <button class="btn btn-primary btn-block" type="submit" id="buyButton">立即秒杀</button> <input type="hidden" name="goodsId" th:value="${goods.id}" /> </form> </td> </tr> <tr> <td>商品原价</td> <td colspan="3" th:text="${goods.goodsPrice}"></td> </tr> <tr> <td>秒杀价</td> <td colspan="3" th:text="${goods.miaoshaPrice}"></td> </tr> <tr> <td>库存数量</td> <td colspan="3" th:text="${goods.stockCount}"></td> </tr> </table> </div> </body> <script> $(function(){ countDown(); }); function countDown(){ var remainSeconds = $("#remainSeconds").val(); var timeout; if(remainSeconds > 0){//秒杀还没开始,倒计时 $("#buyButton").attr("disabled", true); timeout = setTimeout(function(){ $("#countDown").text(remainSeconds - 1); $("#remainSeconds").val(remainSeconds - 1); countDown(); },1000); }else if(remainSeconds == 0){//秒杀进行中 $("#buyButton").attr("disabled", false); if(timeout){ clearTimeout(timeout); } $("#miaoshaTip").html("秒杀进行中"); }else{//秒杀已经结束 $("#buyButton").attr("disabled", true); $("#miaoshaTip").html("秒杀已经结束"); } } </script> </html>
详情页代码没有什么技术难点,主要有一个倒计时的功能,当确定秒杀还没有开始的时候,会显示一个倒计时,代码如下
function countDown(){ var remainSeconds = $("#remainSeconds").val(); var timeout; if(remainSeconds > 0){//秒杀还没开始,倒计时 $("#buyButton").attr("disabled", true); timeout = setTimeout(function(){ $("#countDown").text(remainSeconds - 1); $("#remainSeconds").val(remainSeconds - 1); countDown(); },1000); }else if(remainSeconds == 0){//秒杀进行中 $("#buyButton").attr("disabled", false); if(timeout){ clearTimeout(timeout); } $("#miaoshaTip").html("秒杀进行中"); }else{//秒杀已经结束 $("#buyButton").attr("disabled", true); $("#miaoshaTip").html("秒杀已经结束"); }
该方法通过判断剩余时间,如果剩余时间大于0,则,一直循环减1,如果进入秒杀则清理时间。
立即秒杀按钮是通过form表单提交的,会跳转到处理秒杀的控制器中,实际来说,秒杀有两个步骤,一个减少库存,一个写入订单。这两个步骤需要构成一个事务,如果其中一个出错,就需要回滚。这里可以使用事务注解@Transactional
来解决。
@Controller @RequestMapping("/miaosha") public class MiaoshaController { @Autowired private GoodsService goodsService; @Autowired private OrderService orderService; @Autowired private MiaoshaService miaoshaService; @RequestMapping("/do_miaosha") public String list(Model model, MiaoshaUser user, @RequestParam("goodsId") long goodsId){ model.addAttribute("user",user); if(user==null){ //如果没有获取到user值,就跳转到登录页面去 return "login"; } //判断库存 GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId); Integer stock= goods.getStockCount(); if(stock<=0){ model.addAttribute("errmsg", CodeMsg.MIAO_SHA_OVER.getMsg()); return "miaosha_fail"; } //判断是否已经秒杀到了 MiaoshaOrder order=orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId); if(order!=null){ model.addAttribute("errmsg",CodeMsg.REPEATE_MIAOSHA.getMsg()); return "miaosha_fail"; } //进行秒杀逻辑 //减库存,下订单,写入秒杀订单 OrderInfo orderInfo=miaoshaService.miaosha(user, goods); model.addAttribute("orderInfo",orderInfo); model.addAttribute("goods",goods); return "order_detail"; } }
首先获取user的值,获取不到,则跳转到登录页面,然后从商品数据库中获取库存,若大于0,则继续,否则跳转秒杀失败页面,然后判断是否秒杀商品已经在订单里了,如果在,也跳转到失败页面,因为不能重复秒杀。当前面的都没有问题,才开始进行秒杀操作,首先是减少库存,然后创建新订单。
@Transactional public OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) { //减库存,下订单,写入秒杀订单 goodsService.reduceStock(goods); //抛出异常 // int i=1/0; return orderService.createOrder(user, goods); }
public void reduceStock(GoodsVo goods) { MiaoshaGoods g = new MiaoshaGoods(); g.setGoodsId(goods.getId()); goodsDao.reduceStock(g); }
@Transactional public OrderInfo createOrder(MiaoshaUser user, GoodsVo goods) { OrderInfo orderInfo=new OrderInfo(); //设置订单详情 orderInfo.setCreateDate(new Date()); orderInfo.setDeliveryAddrId(0L); orderInfo.setGoodsCount(1); orderInfo.setGoodsId(goods.getId()); orderInfo.setGoodsName(goods.getGoodsName()); orderInfo.setGoodsPrice(goods.getGoodsPrice()); orderInfo.setGoodsPrice(goods.getGoodsPrice()); orderInfo.setStatus(0); orderInfo.setOrderChannel(1); orderInfo.setUserId(user.getId()); //保存订单 long orderId = orderDao.insert(orderInfo); //保存秒杀订单 MiaoshaOrder miaoshaOrder=new MiaoshaOrder(); miaoshaOrder.setGoodsId(goods.getId()); miaoshaOrder.setOrderId(orderId); miaoshaOrder.setUserId(user.getId()); //抛出异常 //保存秒杀订单 orderDao.insertMiaoshaOrder(miaoshaOrder); return orderInfo; }
至此,一个简单的秒杀功能已经实现,但这样的秒杀逻辑肯定不能抗住高并发,接下来将继续优化。
原文:https://www.cnblogs.com/lovejune/p/12339837.html