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 }
至此秒杀功能基本完成。
posted on 2020-04-09 21:04 hello,bdiskl 阅读(1481) 评论(0) 编辑 收藏 举报