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

自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Resubmission {

    /**
     * 过期时间,默认3秒
     * @return
     */
    long expire() default 3000;

    String prefix() default "";

    String lockKey() default "";
}

定义redis锁相关接口和类

public interface DistributedLock {
    long TIMEOUT_MILLIS = 30000;

    int RETRY_TIMES = 2;

    long SLEEP_MILLIS = 500;

    boolean lock(String key);boolean lock(String key, int retryTimes, long sleepMillis);

    boolean lock(String key, long expire);

    boolean lock(String key, long expire, int retryTimes);

    boolean lock(String key, long expire, int retryTimes, long sleepMillis);

    boolean releaseLock(String key);
}
public abstract class AbstractDistributedLock implements DistributedLock {

    @Override
    public boolean lock(String key) {
        return lock(key , TIMEOUT_MILLIS, RETRY_TIMES, SLEEP_MILLIS);
    }

    @Override
    public boolean lock(String key, int retryTimes, long sleepMillis) {
        return lock(key, TIMEOUT_MILLIS, retryTimes, sleepMillis);
    }

    @Override
    public boolean lock(String key, long expire) {
        return lock(key, expire, RETRY_TIMES, SLEEP_MILLIS);
    }

    @Override
    public boolean lock(String key, long expire, int retryTimes) {
        return lock(key, expire, retryTimes, SLEEP_MILLIS);
    }
}
@Component
public class RedisDistributedLock extends AbstractDistributedLock {
    Logger log = LoggerFactory.getLogger(RedisDistributedLock.class);

    @Autowired
    @Resource
    private RedisTemplate<Object, Object> redisTemplate;

    private ThreadLocal<String> lockFlag = new ThreadLocal<String>();

    public static final String UNLOCK_LUA;

    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }


    public RedisDistributedLock() {
        super();
    }

    @Override
    public boolean lock(String key, long expire, int retryTimes, long sleepMillis) {
        boolean result = setRedis(key, expire);
        // 如果获取锁失败,按照传入的重试次数进行重试
        while ((!result) && retryTimes-- > 0) {
            try {
                Thread.sleep(sleepMillis);
            } catch (InterruptedException e) {
                return false;
            }
            result = setRedis(key, expire);
        }
        return result;
    }

    /**
     *
     * @param key
     * @param expire MILLISECONDS
     * @return
     */
    private boolean setRedis(final String key, final long expire) {
        try {
            String uuid = UUID.randomUUID().toString();
            lockFlag.set(uuid);
            return redisTemplate.opsForValue().setIfAbsent(key,uuid,expire, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            log.info("redis lock error.", e);
        }
        return false;
    }


    @Override
    public boolean releaseLock(String key) {
        // 释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期,此时有可能已经被另外一个线程持有锁,所以不能直接删除
        try {
            DefaultRedisScript<Boolean> defaultRedisScript = new DefaultRedisScript<Boolean>(UNLOCK_LUA,Boolean.class);
            return redisTemplate.execute(defaultRedisScript, Arrays.asList(key),lockFlag.get());
        } catch (Exception e) {
            log.error("release lock occured an exception", e);
        } finally {
            // 清除掉ThreadLocal中的数据,避免内存溢出
            lockFlag.remove();
        }
        return false;
    }
}

定义AOP拦截

@Aspect
@Component
public class ResubmissionAspect {

    @Autowired
    private RedisDistributedLock distributedLock;

    @Pointcut("@annotation(com.neway.wisdommuck.frame.annotation.Resubmission)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        String methodName = method.getName();
        Resubmission resubmission = method.getAnnotation(Resubmission.class);
        String lockKey = resubmission.lockKey();
        String prefix = resubmission.prefix();
        Object elExpression = getByEl(lockKey, point);
        String redisKey = prefix + "_" + methodName + "_" + elExpression;
        //  检查表单是否重复提交
        boolean lock = distributedLock.lock(redisKey, resubmission.expire());
        if (!lock) {
            throw new CommonException("操作太频繁,请稍后再试");
        }
        //  执行方法
        Object proceed = null;
        try {
            proceed = point.proceed();
        } catch (Throwable e) {
            distributedLock.releaseLock(redisKey);
            if (e instanceof CommonException) {
                throw new CommonException(e.getMessage());
            }
            throw new RuntimeException(e);
        }
        distributedLock.releaseLock(redisKey);
        return proceed;
    }

    /**
     * 解析 SpEL 表达式并返回其值
     */
    private Object getByEl(String el, ProceedingJoinPoint point) {
        if (!isEl(el)) {
            return el;
        }
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        String[] paramNames = getParameterNames(method);
        Object[] arguments = point.getArgs();

        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(el);
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < arguments.length; i++) {
            context.setVariable(paramNames[i], arguments[i]);
        }

        return expression.getValue(context, Object.class);
    }

    /**
     * 获取方法参数名列表
     */
    private String[] getParameterNames(Method method) {
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        return u.getParameterNames(method);
    }

    private boolean isEl(String str) {
        return str.contains("#");
    }
}

使用方式

@Resubmission(prefix = "pre", lockKey = "#user.id")
@Override
public R add(User user) {
	// 业务层逻辑
	return R.ok();
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
自定义注解AOP防重复提交是一种通过在代码中添加自定义注解的方式来实现防止重复提交的功能。这种方法可以有效地避免代码耦合性过强,提高代码的可读性和可维护性。 具体实现的方案可以有多种,以下是几种常见的方案: 1. Token验证:在每次请求中添加一个唯一的Token标识,服务端接收到请求后将Token保存在缓存中,然后进行重复提交的验证。如果同一个Token已经存在于缓存中,则表示该请求已经被处理过,可以拒绝重复提交。 2. 请求参数验证:通过对请求参数进行校验,判断是否已经存在相同的请求参数,如果存在则表示重复提交。可以使用缓存或者数据库来存储已经处理过的请求参数,通过查询来进行重复提交的验证。 3. 时间窗口验证:通过设置一个时间窗口,限制在该时间窗口内只接受一次请求。可以使用缓存或者数据库记录请求的时间戳,每次接收到请求时与最近一次的时间戳进行比对,如果在时间窗口内已经存在过请求,则拒绝重复提交。 以上方案都可以使用Redis作为缓存来进行存储和验证操作。可以通过引入相关的依赖来使用Spring Boot集成的Redis组件和Jedis依赖。 通过使用自定义注解AOP来实现防重复提交可以有效地提高代码的可读性和可维护性,同时也能够减轻服务器的负载,避免因为重复提交而导致的服务器宕机等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值