下面的代码是商品秒杀场景,根据用户id和商品id去生成一个标识,然后放入redis缓存中。
@Override public String getMd5(Integer id,Integer userid){ //检验用户的合法性 User user = userMapper.findById(userid); if(user == null) throw new RuntimeException("用户信息不存在!"); log.info("用户信息:[{}]",user.toString()); //检验商品的合法性 Stock stock = stockMapper.checkStock(id); if(stock == null) throw new RuntimeException("商品信息不合法!"); log.info("商品信息:[{}]",stock.toString()); //生成hashkey String hashKey = "KEY_"+userid+"_"+id; //生成md5//这里!QS#是一个盐 随机生成 String key = DigestUtils.md5DigestAsHex((userid+id+"!Q*jS#").getBytes()); stringRedisTemplate.opsForValue().set(hashKey,key,3600, TimeUnit.SECONDS); log.info("Redis写入:[{}][{}]",hashKey,key); return key; }
用户要秒杀商品时,根据用户id和商品id去redis查找有没有对应的签名,如果有则放行,没有就抛出异常。
@Override public int kill(Integer id, Integer userid, String md5) { //校验redis中秒杀商品是否超时 if(!stringRedisTemplate.hasKey("killphone")){ throw new RuntimeException("当前商品的抢购活动已经结束啦~~"); } //先验证签名 String hashKey = "KEY_"+userid+"_"+id; String s = stringRedisTemplate.opsForValue().get(hashKey); if(s==null) throw new RuntimeException("没有携带验证签名,请求不合法!"); if(!s.equals(md5)) throw new RuntimeException("当前请求数据不合法,请稍后再试!"); //校验库存 Stock stock = checkStock(id); //更新库存 updateSale(stock); //创建订单 return createOrder(stock); }2.用redis根据用户id在一定时间范围内统计用户访问次数,超过一定次数就限制访问。
下面这个类是在redis中保存和查询用户访问次数。
@Slf4j @Service @Transactional public class UserServiceImpl implements UserService { @Autowired private StringRedisTemplate stringRedisTemplate; @Override public int saveUserCount(Integer userId) { //根据不同用户id生成调用次数的key String limitKey = "LIMIT"+"_"+userId; //获取redis中指定key的调用次数 String limitNum = stringRedisTemplate.opsForValue().get(limitKey); int limit = -1; if(limitNum == null){ //第一次调用放入redis中设置为0 stringRedisTemplate.opsForValue().set(limitKey,"0",3600, TimeUnit.SECONDS); }else{ //不是第一次调用每次+1 limit = Integer.parseInt(limitNum)+1; stringRedisTemplate.opsForValue().set(limitKey,String.valueOf(limit),3600,TimeUnit.SECONDS); } return limit; } @Override public boolean getUserCount(Integer userId) { //根据userid对应key获取调用次数 String limitKey = "LIMIT"+"_"+userId; //根据用户调用次数的key获取redis中调用次数 String limitNum = stringRedisTemplate.opsForValue().get(limitKey); if(limitNum == null){ //为空直接抛弃说明key出现异常 log.error("该用户没有访问申请验证值记录,疑似异常"); return true; } return Integer.parseInt(limitNum) > 10;//false代表没有超过,true代表超过 } }
当用户在一定时间内访问次数达到限制次数,就不让用户再去访问了。
//开发一个秒杀方法 乐观锁防止超卖+令牌桶算法限流+md5签名(hash接口隐藏)+单用户访问频率限制 @GetMapping("killtokenmd5limit") public String killtokenmd5limit(Integer id,Integer userid,String md5){ System.out.println("秒杀商品的id="+id); //加入 令牌桶的限流措施 if(!rateLimiter.tryAcquire(3, TimeUnit.SECONDS)){ log.info("抛弃请求:抢购失败,当前秒杀活动过于火爆,请重试"); return "抛弃请求:抢购失败,当前秒杀活动过于火爆,请重试!"; } try{ //单用户调用接口的频率限制 int count = userService.saveUserCount(userid); log.info("用户截止该次的访问次数为:[{}]",count); //进行调用次数的判断 boolean isBanned = userService.getUserCount(userid); if(isBanned){ log.info("购买失败,超过频率限制!"); return "购买失败,超过频率限制!"; } //根据秒杀商品id 去调用秒杀业务 int orderId = orderService.kill(id,userid,md5); return "秒杀成功,订单id为:"+String.valueOf(orderId); }catch(Exception e){ e.printStackTrace(); return e.getMessage(); } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)