为了防止用户重复快速提交会导致后台接收到请求多次,产生数据问题甚至报错,因此后端也要做一个防止重复提交的限制。
首先自定义接口@NoRepeatSubmit类:
import java.lang.annotation.*;
/**
* @desc 定义一个不重复提交的注解
* @author lq
* @create 2022年04月22日15:55:07
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NoRepeatSubmit {
/**
* 防止重复参数默认值
* @return
*/
String value() default "";
/**
* 过期时间
* @return
*/
int milliseconds() default 2;
}
自定义缓存类:
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Map 缓存实现
*/
public class MapCache {
/**
* 默认存储1024个缓存
*/
private static final int DEFAULT_CACHES = 1024;
private static final MapCache INS = new MapCache();
public static MapCache single() {
return INS;
}
/**
* 缓存容器
*/
private static Map<String, CacheObject> cachePool;
public MapCache() {
this(DEFAULT_CACHES);
}
public MapCache(int cacheCount) {
cachePool = new ConcurrentHashMap<>(cacheCount);
}
/**
* 读取一个缓存
*
* @param key 缓存key
* @param <T>
* @return
*/
public static <T> T get(String key) {
CacheObject cacheObject = cachePool.get(key);
if (null != cacheObject) {
long cur = System.currentTimeMillis() / 1000;
if (cacheObject.getExpired() <= 0 || cacheObject.getExpired() > cur) {
Object result = cacheObject.getValue();
return (T) result;
}
}
return null;
}
/**
* 读取一个hash类型缓存
*
* @param key 缓存key
* @param field 缓存field
* @param <T>
* @return
*/
public <T> T hget(String key, String field) {
key = key + ":" + field;
return this.get(key);
}
/**
* 设置一个缓存
*
* @param key 缓存key
* @param value 缓存value
*/
public static void set(String key, Object value) {
set(key, value, -1);
}
/**
* 设置一个缓存并带过期时间
*
* @param key 缓存key
* @param value 缓存value
* @param expired 过期时间,单位为秒
*/
public static void set(String key, Object value, long expired) {
expired = expired > 0 ? System.currentTimeMillis() / 1000 + expired : expired;
CacheObject cacheObject = new CacheObject(key, value, expired);
cachePool.put(key, cacheObject);
}
/**
* 设置一个hash缓存
*
* @param key 缓存key
* @param field 缓存field
* @param value 缓存value
*/
public void hset(String key, String field, Object value) {
this.hset(key, field, value, -1);
}
/**
* 设置一个hash缓存并带过期时间
*
* @param key 缓存key
* @param field 缓存field
* @param value 缓存value
* @param expired 过期时间,单位为秒
*/
public void hset(String key, String field, Object value, long expired) {
key = key + ":" + field;
expired = expired > 0 ? System.currentTimeMillis() / 1000 + expired : expired;
CacheObject cacheObject = new CacheObject(key, value, expired);
cachePool.put(key, cacheObject);
}
/**
* 根据key删除缓存
*
* @param key 缓存key
*/
public static void del(String key) {
cachePool.remove(key);
}
/**
* 根据key和field删除缓存
*
* @param key 缓存key
* @param field 缓存field
*/
public void hdel(String key, String field) {
key = key + ":" + field;
this.del(key);
}
/**
* 清空缓存
*/
public void clean() {
cachePool.clear();
}
static class CacheObject {
private String key;
private Object value;
private long expired;
public CacheObject(String key, Object value, long expired) {
this.key = key;
this.value = value;
this.expired = expired;
}
public String getKey() {
return key;
}
public Object getValue() {
return value;
}
public long getExpired() {
return expired;
}
}
}
接口重复提交处理类:
import com.haileer.dd.dingdingserver.entity.Results;
import com.haileer.dd.dingdingserver.utils.MapCache;
import lombok.extern.log4j.Log4j2;
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.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
/**
* @author lq
* @create 2022年04月22日15:59:07
*/
@Aspect
@Component
@Log4j2
public class RepeatSubmitAspect {
@Pointcut("@annotation(NoRepeatSubmit)")
public void pointCut() {
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp) throws BusinessException {
Object result = null;
StringBuilder sb = new StringBuilder();
// 拦截的实体类
Object target = pjp.getTarget();
//获取类名
String className = target.getClass().getName();
sb.append(className);
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
//获得注解
NoRepeatSubmit noRepeatSubmit = method.getAnnotation(NoRepeatSubmit.class);
// 拦截的方法名称。当前正在执行的方法
String methodName = method.getName();
sb.append(methodName);
// 拦截的方法参数
Object[] args = pjp.getArgs();
for (Object arg : args) {
if (arg == null) {
continue;
}
int code = arg.hashCode();
sb.append("#");
sb.append(code);
}
String value = noRepeatSubmit.value();
if (StringUtils.isEmpty(value)) {
value = sb.toString();
}
//获取过期时间
try {
Object count = MapCache.get(value);
MapCache.set(value, 1,2);
if (count != null) {
System.out.println("-----------------------------count="+count);
//重复提交
return new Results(201, "不可重复提交");
}
} catch (Exception e) {
MapCache.del(value);
log.error("redis加锁异常", e);
throw new BusinessException(e.getMessage());
}
try {
//执行原来方法
result = pjp.proceed();
} catch (Throwable e) {
log.error("分布式防重复操作异常Throwable::" + e.getMessage());
e.printStackTrace();
throw new BusinessException(e.getMessage());
}
return result;
}
自定义异常类:
public class BusinessException extends Exception {
public BusinessException(String message){
super(message);
}
}
然后就是使用,只需要在接口上面新增@NoRepeatSubmit这个注释就可以:
可以自行测试哦~