原创 redis实现接口限流
1 核心aop类
@Slf4j
@Aspect
@Component
public class LimitLptAspect {
public static final String LUASCRIPT;
static {
StringBuilder sb = new StringBuilder();
sb.append("local lasttime = redis.call('get', KEYS[1]);");
sb.append("\n ").append("if lasttime then");
sb.append("\n ").append("local addcount = (tonumber(ARGV[3]) - tonumber(lasttime))/1000");
sb.append("\n ").append("local all = redis.call('incrby', KEYS[2], tonumber(ARGV[1]) * math.floor(addcount))");
sb.append("\n ").append("if all > tonumber(ARGV[2]) then");
sb.append("\n ").append("redis.call('set', KEYS[2], ARGV[2])");
sb.append("\n ").append("end");
sb.append("\n ").append("else");
sb.append("\n ").append("redis.call('set', KEYS[2], ARGV[2])");
sb.append("\n ").append("end");
sb.append("\n ").append("local left = tonumber(redis.call('decr', KEYS[2]))");
sb.append("\n ").append("if left >= 0 then");
sb.append("\n ").append("redis.call('set', KEYS[1], ARGV[3])");
sb.append("\n ").append("else");
sb.append("\n ").append("redis.call('set', KEYS[2], 0)");
sb.append("\n ").append("end");
sb.append("\n ").append("redis.call('EXPIRE', KEYS[1], 600)");
sb.append("\n ").append("redis.call('EXPIRE', KEYS[2], 600)");
sb.append("\n ").append("return left");
LUASCRIPT = sb.toString();
}
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Pointcut("@annotation(cn.ascleway.hmjt.server.annotation.LimitLpt)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
// 获取到 HttpServletRequest
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Method method = resolveMethod(point);
// 获取到注解
LimitLpt limitAnnotation = method.getAnnotation(LimitLpt.class);
// 在 redis 中需要使用这个 name 拼接 key
String name = limitAnnotation.name();
Integer maxcount = limitAnnotation.maxCount();
Integer incount = limitAnnotation.inCount();
String ip = IpUtils.getIpAddr2(request);
String key;
LimitType limitType = limitAnnotation.limitType(); // 如不设置则为全局限制 如需按用户 则需要方法参数传入用户id
switch (limitType) {
case IP:
key = limitAnnotation.key() + ip;
break;
case CUSTOMER:
key = limitAnnotation.key();
break;
default:
key = StringUtils.upperCase(method.getName());
}
long curtime = System.currentTimeMillis();
List<String> keys = ImmutableList.of(key + "lasttime", key);
RedisScript<Number> redisScript = new DefaultRedisScript<>(LUASCRIPT, Number.class);
Number left = redisTemplate.execute(redisScript, keys, incount, maxcount, curtime);
log.info("请求ip{},令牌桶key为 {},描述为 [{}] 的接口,剩余数量{}", ip, key, name, left);
if (left != null && left.intValue() >= 0) { // 使用成功则更新使用时间
return point.proceed();
} else {
throw new Exception("访问频率过高,请一分钟后再试");
}
}
private Method resolveMethod(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();//获取执行签名信息
MethodSignature methodSignature = (MethodSignature) signature;
return methodSignature.getMethod();
} }
2 controller方法添加注解
@RestController
@RequestMapping(value="")
@Api(tags = "")
public class WxHomeController {
private static Logger logger = LoggerFactory.getLogger(WxHomeController.class);
@Autowired
private WxHomeService wxHomeService;
@ApiOperation(value = "栏目类别")
@GetMapping("/artCategorys")
@LimitLpt(key = "artCategorys", maxCount = 10, inCount = 1, limitType = LimitType.IP)
public CommonResult<List<CategoryVo>> artCategorys() {
try {
return CommonResult.success(wxHomeService.artCategorys());
} catch (Exception e) {
logger.error("artCategorys.error", e);
return CommonResult.failed(e.getMessage());
}
}
}
令牌桶实现,无需定时器添加令牌,请求时先新增令牌再取令牌。简单实现完成
化繁为简,极致高效。
所有代码为本人原创,转载请联系本人。