基於註解配置與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 DefaultRedisScript<String> getLockRedisScript; private DefaultRedisScript<String> 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<String>(); getLockRedisScript.setResultType(String.class); releaseLockRedisScript = new DefaultRedisScript<String>(); 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、防止重複操作的方法加入註解
超時時間可以自己配置。這個攔截的思路是。某個用戶在指定時間內不能請求相同地址。完美破解同一個用戶在不同瀏覽器同時操作一條數據以及同一個瀏覽器重複單擊按鈕。不足之處請指出。同是學習
同一個瀏覽器