原创 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());
	}
}
}

令牌桶实现,无需定时器添加令牌,请求时先新增令牌再取令牌。简单实现完成

posted @ 2021-06-08 15:16  cris's  阅读(140)  评论(0编辑  收藏  举报