自定义限流注解@RateLimiter

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 自定义限流注解@RateLimiter

定义注解

/**
 * 限流注解
 * 
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimiter
{
    /**
     * 限流key
     */
    public String key() default CacheConstants.RATE_LIMIT_KEY;
    /**
     * 限流时间,单位秒
     */
    public int time() default 60;
    /**
     * 限流次数
     */
    public int count() default 100;
    /**
     * 限流类型
     */
    public LimitType limitType() default LimitType.DEFAULT;
}

对应注解限流处理切面

/**
 * 限流处理
 *
 */
@Aspect
@Component
public class RateLimiterAspect
{
    private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
    private RedisTemplate<Object, Object> redisTemplate;
    private RedisScript<Long> limitScript;
    @Autowired
    public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate)
    {
        this.redisTemplate = redisTemplate;
    }
    @Autowired
    public void setLimitScript(RedisScript<Long> limitScript)
    {
        this.limitScript = limitScript;
    }
    @Before("@annotation(rateLimiter)")
    public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable
    {
        String key = rateLimiter.key();
        int time = rateLimiter.time();
        int count = rateLimiter.count();
        String combineKey = getCombineKey(rateLimiter, point);
        List<Object> keys = Collections.singletonList(combineKey);
        try
        {
            Long number = redisTemplate.execute(limitScript, keys, count, time);
            if (StringUtils.isNull(number) || number.intValue() > count)
            {
                throw new RuntimeException("访问过于频繁,请稍候再试");
            }
            log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key);
        }
        catch (RuntimeException e)
        {
            throw e;
        }
        catch (Exception e)
        {
            throw new RuntimeException("服务器限流异常,请稍候再试");
        }
    }
    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point)
    {
        StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
        if (rateLimiter.limitType() == LimitType.IP)
        {
            stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-");
        }
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();
        stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
        return stringBuffer.toString();
    }
}

LimitType

/**
 * 限流类型
 *
 */
public enum LimitType
{
    /**
     * 默认策略全局限流
     */
    DEFAULT,
    /**
     * 根据请求者IP进行限流
     */
    IP
}

Redis使用FastJson序列化

/**
 * Redis使用FastJson序列化
 * 
 */
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    private Class<T> clazz;
    public FastJson2JsonRedisSerializer(Class<T> clazz)
    {
        super();
        this.clazz = clazz;
    }
    @Override
    public byte[] serialize(T t) throws SerializationException
    {
        if (t == null)
        {
            return new byte[0];
        }
        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }
    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    {
        if (bytes == null || bytes.length <= 0)
        {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);
        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
    }
}

RedisConfig

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport{
    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }
    @Bean
    public DefaultRedisScript<Long> limitScript()
    {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(limitScriptText());
        redisScript.setResultType(Long.class);
        return redisScript;
    }
    /**
     * 限流脚本
     */
    private String limitScriptText()
    {
        return "local key = KEYS[1]\n" +
                "local count = tonumber(ARGV[1])\n" +
                "local time = tonumber(ARGV[2])\n" +
                "local current = redis.call('get', key);\n" +
                "if current and tonumber(current) > count then\n" +
                "    return tonumber(current);\n" +
                "end\n" +
                "current = redis.call('incr', key)\n" +
                "if tonumber(current) == 1 then\n" +
                "    redis.call('expire', key, time)\n" +
                "end\n" +
                "return tonumber(current);";
    }
}

使用@RateLimiter限流

@ApiOperation(value = "查询列表",tags = "学员")
    @PostMapping(value = "/list")
    @RateLimiter(time = 10, count = 10, limitType = LimitType.IP)  //同一个IP,10秒内最多访问10次
    public CommonResult studentList(@RequestBody StudentInfo params){
        PageInfo<StudentInfo> pageList = studentInfoService.getStudentInfoPages(params);
        DicTransferUtils.transfer(pageList);//字典翻译
        return ResultUtil.page(pageList.getList(),(int) pageList.getTotal());
    }
目录
相关文章
|
缓存 Java Sentinel
Springboot 中使用 Redisson+AOP+自定义注解 实现访问限流与黑名单拦截
Springboot 中使用 Redisson+AOP+自定义注解 实现访问限流与黑名单拦截
|
Java Nacos Spring
spring boot 2.6.x接入spring cloud alibaba 2021.x版本nacos
spring cloud alibaba 2021.x版本nacos配置中心对接spring boot 2.6版本。
spring boot 2.6.x接入spring cloud alibaba 2021.x版本nacos
|
存储 算法 NoSQL
接口限流
防止用户恶意刷新接口, 防止对接口的恶意请求,减少不必要的资源浪费,从而保证服务可用。如果不做限流,任由某个用户以非正常方式高频率访问的话,会直接将网络流量、服务器资源挤占完,从而影响正常对外提供服务,造成服务不可用。
346 1
|
6月前
|
分布式计算 运维 监控
Fusion 引擎赋能:流利说如何用阿里云 Serverless Spark 实现数仓计算加速
本文介绍了流利说与阿里云合作,利用EMR Serverless Spark优化数据处理的全过程。流利说是科技驱动的教育公司,通过AI技术提升用户英语水平。原有架构存在资源管理、成本和性能等痛点,采用EMR Serverless Spark后,实现弹性资源管理、按需计费及性能优化。方案涵盖数据采集、存储、计算到查询的完整能力,支持多种接入方式与高效调度。迁移后任务耗时减少40%,失败率降低80%,成本下降30%。未来将深化合作,探索更多行业解决方案。
330 1
|
Kubernetes Cloud Native 开发者
云原生入门:Kubernetes的简易指南
【10月更文挑战第41天】本文将带你进入云原生的世界,特别是Kubernetes——一个强大的容器编排平台。我们将一起探索它的基本概念和操作,让你能够轻松管理和部署应用。无论你是新手还是有经验的开发者,这篇文章都能让你对Kubernetes有更深入的理解。
|
Java Android开发
IDEA设置项目编码格式【修改为GBK 或 UTF-8】
这篇文章介绍了在IntelliJ IDEA中如何设置项目编码格式,包括将项目编码修改为GBK或UTF-8的详细步骤和图解。
20595 12
IDEA设置项目编码格式【修改为GBK 或 UTF-8】
|
数据安全/隐私保护
sm4加密工具类
sm4加密工具类
195 4
|
Linux 数据安全/隐私保护 Windows
解决Windows密码丢失问题:详细指南
最近因为某些工作缘故,接触到windows比较频繁,特此记录一下 当下,计算机安全是每个人都不能忽视的重要问题。然而,有时可能因为忘记密码而无法访问自己的Windows系统,这会导致数据和信息的临时不可用。 本文将详细介绍两种场景下的密码恢复方法:一种是针对虚拟机,另一种适用于物理机。通过这些方法,可以快速恢复对系统的访问,确保业务的连续性。
解决Windows密码丢失问题:详细指南
|
存储 数据库 索引
B树与B+树区别
B树与B+树区别
3286 1
|
前端开发 数据库
基于若依的ruoyi-nbcio流程管理系统增加流程节点配置(二)
基于若依的ruoyi-nbcio流程管理系统增加流程节点配置(二)
382 1