工作原理
分布式环境下,可能会遇到用户对某个接口被重复点击的场景,为了防止接口重复提交造成的问题,可用 Redis 实现一个简单的分布式锁来解决问题。
在 Redis 中, SETNX
命令是可以帮助我们实现互斥。SETNX 即 SET if Not eXists (对应 Java 中的 setIfAbsent
方法),如果 key 不存在的话,才会设置 key 的值。如果 key 已经存在, SETNX 啥也不做。
需求实现
- 自定义一个防止重复提交的注解,注解中可以携带到期时间和一个参数的key
- 为需要防止重复提交的接口添加注解
- 注解AOP会拦截加了此注解的请求,进行加解锁处理并且添加注解上设置的key超时时间
- Redis 中的
key = token + "-" + path + "-" + param_value;
(例如:17800000001 + /api/subscribe/ + zhangsan) - 如果重复调用某个加了注解的接口且key还未到期,就会返回重复提交的Result。
1)自定义防重复提交注解
自定义防止重复提交注解,注解中可设置 超时时间 + 要扫描的参数(请求中的某个参数,最终拼接后成为Redis中的key)
package com.lihw.lihwtestboot.noRepeatSubmit;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 防重复提交注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {
/**
* 锁过期的时间
*/
int seconds() default 5;
/**
* 要扫描的参数
*/
String scanParam() default "";
}
2)定义防重复提交AOP切面
@Pointcut("@annotation(noRepeatSubmit)")
表示切点表达式,它使用了注解匹配的方式来选择被注解 @NoRepeatSubmit
标记的方法。
package com.lihw.lihwtestboot.noRepeatSubmit;
import com.alibaba.fastjson.JSONObject;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.UUID;
/**
* 重复提交aop
*/
@Aspect
@Component
public class RepeatSubmitAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(RepeatSubmitAspect.class);
@Autowired
private RedisLock redisLock;
@Pointcut("@annotation(noRepeatSubmit)")
public void pointCut(NoRepeatSubmit noRepeatSubmit) {
}
@Around("pointCut(noRepeatSubmit)")
public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
//获取基本信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Assert.notNull(request, "request can not null");
int lockSeconds = noRepeatSubmit.seconds();//过期时间
String threadName = Thread.currentThread().getName();// 获取当前线程名称
String param = noRepeatSubmit.scanParam();//请求参数
String path = request