说明:之前写过一篇使用Redis接口访问的博客,如下。最近有相关需求,把代码拿出来后,做了一些优化,挺有意思的,本文介绍在原基础上
优化
总的来说,这次使用Redis实现接口防抖,增加了一个时间段参数,可以限制接口在某个时间段内,访问不能超过多少次。如下:
(自定义注解,打在接口上)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定义注解 */ @Target (ElementType.METHOD) @Retention (RetentionPolicy.RUNTIME) public @interface LimitAccess { /** * 限制访问的key * @return */ String key(); /** * 限制访问次数 * @return */ int times(); /** * 时间段 * @return */ int duration(); } |
(切面,实现限制访问)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | import com.hezy.annotation.LimitAccess; import lombok.extern.log4j.Log4j2; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; /** * AOP类(通知类) */ @Component @Aspect @Log4j2 public class LimitAspect { @Value ( "${access.enable:false}" ) private boolean enable; @Autowired private RedisTemplate redisTemplate; @Pointcut ( "@annotation(com.hezy.annotation.LimitAccess)" ) public void pt(){}; @Around ( "pt()" ) public Object aopAround(ProceedingJoinPoint pjp) throws Throwable { // 设置一个开关,控制是否执行 if (!enable) { return pjp.proceed(); } // 获取切入点上面的自定义注解 Signature signature = pjp.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; // 获取方法上面的注解 LimitAccess limitAccess = methodSignature.getMethod().getAnnotation(LimitAccess. class ); // 获取注解上面的属性值 int limit = limitAccess.times(); String key = limitAccess.key(); int duration = limitAccess.duration(); // 递增键的值,如果键不存在则初始化为1 Long currentCount = redisTemplate.opsForValue().increment(key, 1 ); // 如果键是新创建的,设置过期时间 if (currentCount != null && currentCount == 1 ) { redisTemplate.expire(key, duration, TimeUnit.SECONDS); } // 检查是否超过限制 if (currentCount != null && currentCount > limit) { log.info( "访问过于频繁: " + pjp.toLongString()); throw new RuntimeException( "访问过于频繁" ); } return pjp.proceed(); } } |
(使用,在对应的接口上,打上注解,填上数值,如下表示,1秒内不能访问超过3次)
1 2 3 4 5 | @LimitAccess (key = "test" , times = 3 , duration = 1 ) @GetMapping public String test() { return demoService.test(); } |
另外,在代码中加了一个开关,可在配置文件中设置此配置,表示开启或者关闭,默认是关闭的
1 2 | access: enable: true |
启动项目,测试一下
思考
以上代码,有两点需要思考:
-
注解能不能加在Service层的方法上,加了有没有用?另外加了会不会让声明式事务失效?
-
这个限制,没有到用户的维度,也就是说所有的用户,只要在一个时间段内访问超过次数就限制,这显然是不行的。有什么办法吗?
第一点,我测试过,注解可以加在Service层方法上,是可以的,不会让事务失效,如下:
1 2 3 4 5 6 7 8 9 10 11 12 | @LimitAccess (key = "deleteUserById" , times = 3 , duration = 1 ) @Transactional @Override public void deleteUserById(Integer id) { // 删除用户 userMapper.deleteUserById(id); int i = 1 / 0 ; // 删除用户对应的角色 userMapper.deleteUserRoleMapper(id); } |
接口限制,事务,都生效了。
第二点,这确实是个问题,要做到针对用户层面的接口限制是必须的,不然有一个用户恶意刷新,其他用户都用不了了,这怎么可以。要解决这个问题,首先要拿到当前操作用的标识,用户名、用户ID,然后再存入key的时候,拼接上这个标识作为key。
1 | String key = limitAccess.key() + 用户标识(用户名、用户id); |
获取当前用户信息,参考下面这篇文章:
如果你不是自己从0开发项目,一般成熟的项目都会有获取当前操作用户信息的方式的。实在不行,你让前端把用户id作为参数传给你,你在切面里获取这个用户id都可以。
总结
本文是对之前用Redis接口访问的优化,以及对两个问题的考虑,希望能对大家有启发。
获取源码:https://github.com/HeZhongYing/limit_access_demo
到此这篇关于Redis接口访问优化的方法步骤的文章就介绍到这了,更多相关Redis接口访问优化内容请搜索IT俱乐部以前的文章或继续浏览下面的相关文章希望大家以后多多支持IT俱乐部!