zoukankan      html  css  js  c++  java
  • java初探(1)之秒杀的业务简单实现

    • 前言

      秒杀的业务场景广泛存在于电商当中,即有一个倒计时的时间限制,当倒计时为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>
    View Code

    从商品页面代码,点击详情的超链接可以到达去商品详情的控制器

    @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>
    View Code

    详情页代码没有什么技术难点,主要有一个倒计时的功能,当确定秒杀还没有开始的时候,会显示一个倒计时,代码如下

    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";
        }
    }
    View Code

     首先获取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;
        }

    至此,一个简单的秒杀功能已经实现,但这样的秒杀逻辑肯定不能抗住高并发,接下来将继续优化。

  • 相关阅读:
    [npm]npm audit fix
    [javascript]中央定时器控制
    [javascript]并发模型与事件循环(Concurrency model and Event loop)
    [翻译][JavaScript] JavaScript 是不是一定是单线程的?
    [DOM][穿梭框][js]运用document.adoptNode方法,写出基础的穿梭框效果
    [document][DOM]document.importNode 与 document.adoptNode
    [DOM][document][进阶]DocumentFragment, document.createDocumentFragment()
    [Object][进阶]Object.defineProperty(),Object.defineProperties(),Object.getOwnPropertyDescriptor()
    [js][字符串]给字符串去空格(全角和半角)
    [vue]mixins在项目中的应用
  • 原文地址:https://www.cnblogs.com/lovejune/p/12339837.html
Copyright © 2011-2022 走看看