基于注解配置与aop完美结合。在指定时间内。用redis+lua脚本获取锁。不会出现插队。看代码实现。
1、创建AopInterceptor
/** * @author:wwz */ @Aspect @Component public class AopInterceptor{ @Autowired private RedisUtils redisUtils; private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Pointcut("@annotation(com.ditop.modules.gcjsy.company.wsba.constant.RejectMultiPost)") public void pointcut(){ } private final static class Holder{ private static HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); } @Around("pointcut()&& @annotation(rejectMultiPost)") public Object around(ProceedingJoinPoint joinPoint, RejectMultiPost rejectMultiPost){ String key = null; try { key = PermissionHelper.currentUserSession().getUserId()+Holder.request.getRequestURI(); //这里是redis+lua。一个key在指定时间内。只获取一次。不会出现插队 if(redisUtils.lock(key,key,rejectMultiPost.timeout())){ return joinPoint.proceed(joinPoint.getArgs()); }else{ logger.error("重复提交【代码未执行】"); System.out.println("重复提交【代码未执行】"); } } catch (Throwable throwable) { logger.error(throwable.getMessage()); throwable.printStackTrace(); if(key!=null){ redisUtils.unLock(key,key); }else{ try { return joinPoint.proceed(joinPoint.getArgs()); } catch (Throwable throwable1) { logger.error(throwable1.getMessage()); throwable1.printStackTrace(); } } } return JsonResult.success(); } }
2、创建RedisUtils
/** * @author wwz * @date 2020-05-15 * @descrption: */ @Component public class RedisUtils { private final Logger log = LoggerFactory.getLogger(this.getClass()); /* private final String expireTime = "5000";*/ @SuppressWarnings("rawtypes") @Autowired private RedisTemplate stringRedisTemplateDemo; private DefaultRedisScriptgetLockRedisScript; private DefaultRedisScript releaseLockRedisScript; private StringRedisSerializer argsStringSerializer = new StringRedisSerializer(); private StringRedisSerializer resultStringSerializer = new StringRedisSerializer(); private final String EXEC_RESULT = "1"; @SuppressWarnings("unchecked") @PostConstruct private void init() { getLockRedisScript = new DefaultRedisScript (); getLockRedisScript.setResultType(String.class); releaseLockRedisScript = new DefaultRedisScript (); releaseLockRedisScript.setResultType(String.class); // 初始化装载 lua 脚本 getLockRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/lock.lua"))); releaseLockRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/releaseLock.lua"))); } /** * 加锁操作 * @param key Redis 锁的 key 值 * @param requestId 请求id,防止解了不该由自己解的锁 (随机生成) * @param expireTime 锁的超时时间(毫秒) * @param retryTimes 获取锁的重试次数 * @return true or false */ @SuppressWarnings("unchecked") public boolean lock(String key, String requestId, String expireTime) { int retryTimes = 1; try { int count = 0; while (true) { Object result = stringRedisTemplateDemo.execute(getLockRedisScript, argsStringSerializer, resultStringSerializer, Collections.singletonList(key), requestId, expireTime); log.debug("result:{},type:{}", result, result.getClass().getName()); if (EXEC_RESULT.equals(result)) { return true; } else { count++; if (retryTimes == count) { log.warn("has tried {} times , failed to acquire lock for key:{},requestId:{}", count, key, requestId); return false; } else { log.warn("try to acquire lock {} times for key:{},requestId:{}", count, key, requestId); Thread.sleep(500); continue; } } } } catch (InterruptedException e) { e.printStackTrace(); } return false; } /** * 解锁操作 * @param key Redis 锁的 key 值 * @param requestId 请求 id, 防止解了不该由自己解的锁 (随机生成) * @return true or false */ @SuppressWarnings("unchecked") public boolean unLock(String key, String requestId) { Object result = stringRedisTemplateDemo.execute(releaseLockRedisScript, argsStringSerializer, resultStringSerializer, Collections.singletonList(key), requestId); if (EXEC_RESULT.equals(result)) { return true; } return false; } }
3、引入lua脚本。放到resource目录下
lock.lua:
if redis.call('set',KEYS[1],ARGV[1],'NX','PX',ARGV[2]) then return '1' else return '0' end
releaseLock.lua:
if redis.call('get',KEYS[1]) == ARGV[1] then return tostring(redis.call('del',KEYS[1])) else return '0' end
4、创建注解:RejectMultiPost
@Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RejectMultiPost { /** * ms * @return */ String timeout() default "5000"; }
5、防止重复操作的方法加入注解
超时时间可以自己配置。这个拦截的思路是。某个用户在指定时间内不能请求相同地址。完美破解同一个用户在不同浏览器同时操作一条数据以及同一个浏览器重复单击按钮。不足之处请指出。同是学习
同一个浏览器