自定义注解防止接口重复提交

实现思路:

1.自定义防重复提交的注解和切面

2.在需要验证的接口上增加注解(一般是创建、修改的接口)

3.以每次调用的 用户唯一标识(userId或者sessionId或者token)+ 请求路径+参数 作为key,value任意值都可以,缓存起来(redis或本地缓存),并设置一个合适的缓存失效时间。

4.每次调用时根据key判断,缓存是否存在,存在则抛出异常或提示,不存在则执行业务逻辑

声明自定义注解:

package com.***.annotation;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * <p>Description: [防重复提交注解]</p >
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {

    /**
     * 过期时长(毫秒)
     *
     * @return
     */
    long expire() default 500;

}

使用redissionClient实现分布式锁(可重入锁):

package com.***.util;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

@Slf4j
public class RedisLock {

    /**
     * 默认等待时长 1 毫秒
     */
    private static final long DEF_WAIT_TIME = 1L;

    /**
     * 默认过期时长 10 秒
     */
    private static final long DEF_EXPIRE_TIME = 1000 * 10L;

    /**
     * 默认重试次数
     */
    private static final Integer NO_TRY_COUNT = 0;
    /**
     * 默认重试休眠时长
     */
    private static final Long NO_TRY_SLEEP_TIME = 0L;

    private RedisLock() {
    }

    /**
     * 获取分布式锁
     *
     * @param redissonClient
     * @param lockKey        锁标识
     * @return
     */
    public static RLock getLock(RedissonClient redissonClient, String lockKey) {
        return redissonClient.getLock(lockKey);
    }

    /**
     * 尝试加锁
     *
     * @param lock 锁
     * @return
     */
    public static boolean lock(RLock lock) {
        return lock(lock, null);
    }

    /**
     * 尝试加锁
     *
     * @param lock       锁
     * @param conditions 附加条件
     * @return
     */
    public static boolean lock(RLock lock, Supplier<Boolean> conditions) {
        return lock(lock, NO_TRY_COUNT, NO_TRY_SLEEP_TIME, conditions);
    }

    /**
     * 尝试加锁
     *
     * @param lock       锁
     * @param tryCount   重试次数
     * @param sleepTime  重试休眠时长(毫秒)
     * @param conditions 附加条件
     * @return
     */
    public static boolean lock(RLock lock, int tryCount, long sleepTime, Supplier<Boolean> conditions) {
        return lock(lock, DEF_WAIT_TIME, DEF_EXPIRE_TIME, tryCount, sleepTime, conditions);
    }

    /**
     * 尝试加锁
     *
     * @param lock       锁
     * @param waitTime   获取锁等待时长(毫秒)
     * @param expireTime 获取锁后自动过期时长(毫秒)
     * @param tryCount   重试次数
     * @param sleepTime  重试休眠时长(毫秒)
     * @param conditions 附加条件
     * @return
     */
    public static boolean lock(RLock lock, long waitTime, long expireTime, int tryCount,
                               long sleepTime, Supplier<Boolean> conditions) {
        boolean result = false;
        try {
            boolean unlock = lock.tryLock(waitTime, expireTime, TimeUnit.MILLISECONDS);
            log.info("======> " + Thread.currentThread() + "尝试{}获取锁{} -> {}", tryCount, lock.getName(), unlock);
            if (unlock) {
                if (null != conditions) {
                    result = conditions.get();
                } else {
                    result = true;
                }
            } else {
                while (tryCount > 0) {
                    Thread.sleep(sleepTime);
                    --tryCount;
                    boolean res = lock(lock, waitTime, expireTime, tryCount, sleepTime, conditions);
                    if (res) {
                        result = true;
                        break;
                    }
                }
            }
        } catch (InterruptedException e) {
            log.error("RedisLock 获取分布式锁异常 -> {}", e);
            Thread.currentThread().interrupt();
        }
        return result;
    }

    /**
     * 释放锁
     *
     * @param lock
     */
    public static void unlock(RLock lock) {
        if (null != lock) {
            log.info("<====== " + Thread.currentThread() + "释放锁{}", lock.getName());
            lock.unlock();
        }
    }

}

注解处理实现类: 

package com.***.annotation;

import com.***.MD5Util;
import com.***.CacheKeyBuild;
import com.***.CacheService;
import com.***.ResultCodeEnum;
import com.***.RedisLock;
import com.***.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
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.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
@Slf4j
public class NoRepeatSubmitAop {

    private static final String TOKEN_KEY = "Authorization";

    @Autowired
    private CacheService cacheService;

    @Autowired
    private RedissonClient redissonClient;

    @Pointcut("@annotation(com.asp.alc.prepose.annotation.NoRepeatSubmit)")
    public void serviceNoRepeat() {
        // 这是一个标记方法
    }

    @Around("serviceNoRepeat()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        HttpServletRequest request =((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request.getHeader(TOKEN_KEY);
        String url = request.getRequestURL().toString();
        String params = Arrays.toString(pjp.getArgs());
        String key =  CacheKeyBuild.buildSysAccountKey(MD5Util.MD(token + "-" + url + "-" + params));
        // 这里使用分布锁保证其线程安全(这里很关键)
        String lockKey = "Lock-" + key;
        RLock lock = RedisLock.getLock(redissonClient, lockKey);
        boolean res = RedisLock.lock(lock);
        if(!res) {
            throw new ServiceException(ResultCodeEnum.CODE_10007.code, ResultCodeEnum.CODE_10007.desc);
        }
        Optional<String> cacheValue = Optional.ofNullable(cacheService.getString(key));
        log.info("======> " + Thread.currentThread() + "NoRepeatSubmitAop {} -> {}", key, cacheValue.isPresent());
        if (!cacheValue.isPresent()) {
            try {
                Object o = pjp.proceed();
                MethodSignature signature = (MethodSignature) pjp.getSignature();
                NoRepeatSubmit noRepeatSubmit = signature.getMethod().getAnnotation(NoRepeatSubmit.class);
                // 默认500毫秒内同一认证用户同一个地址同一个参数,视为重复提交
                cacheService.set(key, "******", noRepeatSubmit.expire(), TimeUnit.MILLISECONDS);
                return o;
            }catch (ServiceException e){
                log.error(e.getMessage(), e);
                throw e;
            }catch (Exception e){
                log.error(ResultCodeEnum.CODE_10000.desc, e);
                throw new ServiceException(ResultCodeEnum.CODE_10000.code, e.getMessage());
            } finally {
                RedisLock.unlock(lock);
            }
        } else {
            RedisLock.unlock(lock);
            throw new ServiceException(ResultCodeEnum.CODE_10007.code, ResultCodeEnum.CODE_10007.desc);
        }
    }

}

ok了·!!!

猜你喜欢

转载自blog.csdn.net/SHYLOGO/article/details/107635034