zoukankan      html  css  js  c++  java
  • Java秒杀系统--5.秒杀功能模块

    简介

      实现秒杀接口

    1.访问秒杀接口

      在秒杀商品详情页面中,点击立即秒杀按钮就可以访问秒杀接口了。

      onclick="getPath()" id="buyButton">开始秒杀</button>:调用getPath(),因为对秒杀接口进行了接口隐藏,所以要先获取path值,然后到url中,才能对秒杀接口进行访问。

    2.后端处理获取path的请求,起到隐藏秒杀接口的功能(没用,不如在后端进行时间判断)

      

    1 @AccessLimit(seconds = 120,maxCount = 5)
    2     @RequestMapping(value = "/getPath",method = RequestMethod.GET)
    3     @ResponseBody
    4     public Result<String> getPath(Model model,MiaoshaUser user, @RequestParam("goodsId")long goodsId) {
    5         String path = MD5Util.md5(UUIDUtil.UUID());
    6         redisService.set(PathPrefix.Path,user.getId()+"_"+goodsId,path);
    7         return Result.success(path);
    8     }

    path:一个随机字符串,然后进行一次MD5加密(MD5加密好像可有可无)得到的字符串。

    对path使用了redis缓存。

    隐藏秒杀接口的目的:为了防止恶意用户提前访问秒杀商品。普通用户在秒杀时间来到时,才可以点击秒杀按钮访问秒杀接口,因为前端设计为在时间未到时秒杀按钮是无效的;恶意用户可能通过查看网页源码得到秒杀接口,在秒杀时间未到时访问秒杀接口。

    没用:可以写一个脚本,先获得path变量完成拼接,再访问秒杀接口;还不如在后端添加一个时间判断。

    3.前端获的path的值,开始访问秒杀接口

     

     1 success:function(data) {
     2             //获得path参数后,调用秒杀接口函数
     3             go_miaosha(data.data)
     4         },
     5 //调用秒杀接口
     6     function go_miaosha(path) {
     7         $.ajax({
     8             url:"/miaosha/"+path+"/do_miaosha",
     9             type:"POST",
    10             data:{
    11                 goodsId:$("#goodsId").val(),
    12             },
    13             success:function (data){
    14                 if (data.code==0){
    15                     getResult($("#goodsId").val());
    16                 }else layer.msg(data.msg)
    17             },
    18             error:function () {
    19                 layer.msg("客户端有误1")
    20             }
    21         });
    22     }

    4.后端秒杀接口

      第一个验证:限制某个用户在5秒钟内,不能调用该接口超过5次,并且必须进行登录。

      @AccessLimit(seconds = 5,maxCount = 1,needLogin = true)

      第二个验证:验证url中的path值是否相同,path为一个字符串,被存储在redis中,key由userId和goodsId构成,生存期为60秒。

      访问redis得到其中存储的path值,然后进行比较。

      

    1 @AccessLimit(seconds = 120,maxCount = 5)
    2     @RequestMapping(value = "/getPath",method = RequestMethod.GET)
    3     @ResponseBody
    4     public Result<String> getPath(Model model,MiaoshaUser user, @RequestParam("goodsId")long goodsId) {
    5         String path = MD5Util.md5(UUIDUtil.UUID());
    6         redisService.set(PathPrefix.Path,user.getId()+"_"+goodsId,path);
    7         return Result.success(path);
    8     }

      第三个验证:验证商品的库存数量,利用afterPropertiesSet方法,在spring容器初始化时将数据库中的商品数量缓存到redis中。

      

     1 //该方法在初始化之前执行,将数据库中的商品库存缓存到redis服务器中
     2     private Map<Long,Boolean> goodsStatus = new HashMap<>();
     3     @Override
     4     public void afterPropertiesSet() throws Exception {
     5         List<GoodsVo> goodsList = miaoshaGoodsService.getGoodsList();
     6         for (GoodsVo goods:goodsList
     7              ) {
     8             long id = goods.getId();
     9             long stock = goods.getStockCount();
    10             goodsStatus.put(id,false);
    11             redisService.set(GoodsPrefix.getGoodsStack,""+id,stock);
    12         }
    13     }

      当redis中的商品库存被减到零后,所有后来的秒杀请求均被拒绝,并且只会触发一次集合的get操作,连redis操作也不会触发。

      

    1     if (goodsStatus.get(goodsId)) {
    2             return Result.error(CodeMsg.SECKILL_OVER);
    3         }
    4         long stock = redisService.decr(GoodsPrefix.getGoodsStack, "" + goodsId);
    5         if (stock < 0) {
    6             goodsStatus.replace(goodsId, true);
    7             return Result.error(CodeMsg.SECKILL_OVER);
    8         }

      第四个验证:验证是否包含该用户秒杀该商品的订单,每当完成一次秒杀。都将秒杀订单缓存到redis中,方便快速验证。

    1                                                                                                                         //验证4.判断是否有该用户和该商品的订单存在
    2         MiaoshaOrder miaoOrder = redisService.get(OrderPrefix.getGoodIdAndUserId, "" + user.getId() + goodsId, MiaoshaOrder.class);
    3         if (miaoOrder != null) {
    4             return Result.error(CodeMsg.REPEATE_SECKILL);
    5         }

      使用rabbitmq将秒杀操作信息封装发送到接收端,然后执行秒杀的动作。使用MiaoshaMessage.class来封装秒杀操作信息。

    1 import lombok.Data;
    2 
    3 @Data
    4 public class MiaoshaMessage {
    5     private MiaoshaUser miaoshaUser;
    6     private long goodsId;
    7 }
    1      MiaoshaMessage mm = new MiaoshaMessage();
    2         mm.setGoodsId(goodsId);
    3         mm.setMiaoshaUser(user);
    4         mqSender.sendMiaoshaMessage(mm);

      在监听器端还有验证一下数据库中记录的库存,然后再执行秒杀,第五个验证:验证数据库中的商品库存数量。

    @RabbitListener(queues = MQConfig.MIAOSHA_QUEUE)
        public void reciveMiaosha(String msg) {
            MiaoshaMessage mm = RedisService.strToBean(msg, MiaoshaMessage.class);
            MiaoshaUser user = mm.getMiaoshaUser();
            long goodsId = mm.getGoodsId();
                                                                                                                            //通过数据库中的数据来判断库存的多少
            GoodsVo goodsVo = miaoshaGoodsService.getGoodsVoById(goodsId);
            int kucun = goodsVo.getStockCount();
            if (kucun == 0) {
                return;
            }
            //进行数据库中的秒杀操作
            miaoshaService.miaoSha(user.getId(), goodsId);
        }

     5.使用MiaoshaService.class中的miaoSha(long useId, long goodsId)方法来完成秒杀工作。

      在该方法上使用@Transactional标签,当事务来处理。总共分为3大步,减库存,下订单,redis缓存秒杀订单。

      

     1 @Transactional
     2     public OrderInfo miaoSha(long useId, long goodsId) {
     3                                                                                                                         //减库存,并设置减库存的结果
     4         boolean reduceResult=miaoshaGoodsService.reduceStock(goodsId);
     5         setReduceResult(goodsId,reduceResult);
     6         miaoshaGoodsService.reduceFMStock(goodsId);
     7                                                                                                                         //下订单
     8         orderInfoService.createOrder(useId,goodsId);
     9         long orderId = orderInfoService.getByUserIdGoodsId(useId,goodsId).getId();
    10         MiaoshaOrder miaoshaOrder = new MiaoshaOrder();
    11         miaoshaOrder.setMiaoshagoodsId(goodsId);
    12         miaoshaOrder.setOrderId(orderId);
    13         miaoshaOrder.setUserId(useId);
    14         orderInfoService.createMiaoshaOrder(miaoshaOrder);
    15                                                                                                                         //为秒杀订单做一个redis缓存
    16         redisService.set(OrderPrefix.getGoodIdAndUserId,""+useId+goodsId,miaoshaOrder);
    17         return orderInfoService.getByUserIdGoodsId(useId,goodsId);
    18     }

    至此秒杀功能基本完成。

      

     

      

  • 相关阅读:
    jQuery基础之让出$,与其他库共存
    什么是闭包
    绑定repeater时三目运算加特殊结果处理
    将同一张表出来的两部分内容再合成一张表
    后台往前台写弹窗代码不显示
    固定行列转换加分段统计
    js调用后台方法(如果你能容忍执行的后台方法变成一个常量)
    javascript遍历数组
    基于SpringMVC框架使用ECharts3.0实现折线图,柱状图,饼状图,的绘制(上篇)
    echarts
  • 原文地址:https://www.cnblogs.com/deijiawoyu/p/12669131.html
Copyright © 2011-2022 走看看