簡介
實現秒殺接口
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 }
至此秒殺功能基本完成。