java 後端做重複提交攔截基於aop

基於註解配置與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、防止重複操作的方法加入註解

 

超時時間可以自己配置。這個攔截的思路是。某個用戶在指定時間內不能請求相同地址。完美破解同一個用戶在不同瀏覽器同時操作一條數據以及同一個瀏覽器重複單擊按鈕。不足之處請指出。同是學習

同一個瀏覽器

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章