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;
    }

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

原文地址:https://www.cnblogs.com/lovejune/p/12339837.html