一:实现方法
1.自定义防重复提交的注解和切面
2.在需要验证的接口上增加注解(一般是创建、修改的接口)
3.以每次调用的 类名+方法名+请求数据 的MD5值作为key,value任意值都可以,缓存起来(redis或本地缓存或其他),并设置一个合适的缓存失效时间。
4.每次调用时根据key判断,缓存是否存在,存在则抛出异常或提示,不存在则执行业务逻辑。
二:防重复提交注解
package com.platform.cloudlottery.aop;
import java.lang.annotation.*;
/**
* @author : chenkun
* @version V1.0
* @Project: cloudlottery-interface
* @Package com.platform.cloudlottery.aop
* @Description: TODO(这里用一句话描述这个类的作用)
* @date Date : 2019年10月16日 11:26
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmitCheck {
int keepSeconds() default 60;
}
三:切面
package com.platform.cloudlottery.aop;
import com.alibaba.fastjson.JSON;
import com.platform.cloudlottery.common.codec.Md5Utils;
import com.platform.cloudlottery.common.redis.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @author : chenkun
* @version V1.0
* @Project: cloudlottery-interface
* @Package com.platform.cloudlottery.aop
* @Description: TODO(防止重复提交请求)
* @date Date : 2019年10月16日 11:24
*/
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {
@Resource
private RedisService redisService;
@Pointcut("@annotation(RepeatSubmitCheck)")
public void requestPointcut() {
}
@Before("requestPointcut() && @annotation(repeatSubmitCheck)")
public void aroundCheck(JoinPoint joinPoint, RepeatSubmitCheck repeatSubmitCheck) {
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
String paramsJsonStr = JSON.toJSONString(args);
String srcStr = className + "_" + methodName + "_" + paramsJsonStr;
String signStr = Md5Utils.md5(srcStr);
log.info("防止重复提交的请求 Redis Key:"+signStr);
//判断缓存是否存在不存在则添加缓存
if(!redisService.exists(signStr)){
redisService.setex(signStr, repeatSubmitCheck.keepSeconds(), "1");
} else {//重复请求
log.info("重复提交的请求数据:"+srcStr);
throw new RuntimeException("重复请求");
}
}
}
推荐使用Redis。
四:RedisService类
package com.platform.cloudlottery.common.redis;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.util.List;
import java.util.Map;
@AllArgsConstructor
public class RedisService {
private RedisTemplate<String, Object> redisTemplate;
public boolean set(final String key, final String value) {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
return connection.set(serializer.serialize(key), serializer.serialize(value));
});
}
public boolean setNx(final String key, final String value) {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
return connection.setNX(serializer.serialize(key), serializer.serialize(value));
});
}
public boolean setex(final String key, long expire, final String value) {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
return connection.setEx(serializer.serialize(key), expire, serializer.serialize(value));
});
}
public String get(final String key) {
return redisTemplate.execute(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
byte[] value = connection.get(serializer.serialize(key));
return serializer.deserialize(value);
}
});
}
public String getSet(final String key, final String value){
return redisTemplate.execute((RedisCallback<String>) connection -> {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
byte[] preValue = connection.getSet(serializer.serialize(key), serializer.serialize(value));
return serializer.deserialize(preValue);
});
}
public Long del(final String key) {
return redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
return connection.del(key.getBytes());
});
}
public boolean expire(final String key, long expire) {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> connection.expire(key.getBytes(),expire));
}
public <T> boolean setList(String key, List<T> list) {
String value = JSON.toJSONString(list);
return set(key, value);
}
public <T> List<T> getList(String key, Class<T> clz) {
String json = get(key);
if (json != null) {
List<T> list = JSON.parseArray(json, clz);
return list;
}
return null;
}
public long lpush(final String key, Object obj) {
final String value = JSON.toJSONString(obj);
long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
long count = connection.lPush(serializer.serialize(key), serializer.serialize(value));
return count;
});
return result;
}
public long rpush(final String key, Object obj) {
final String value = JSON.toJSONString(obj);
long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
long count = connection.rPush(serializer.serialize(key), serializer.serialize(value));
return count;
});
return result;
}
public String lpop(final String key) {
String result = redisTemplate.execute((RedisCallback<String>) connection -> {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
byte[] res = connection.lPop(serializer.serialize(key));
return serializer.deserialize(res);
});
return result;
}
public boolean exists(String key) {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
return connection.exists(key.getBytes());
});
}
public Long incrBy(final String key, long value) {
return redisTemplate.execute((RedisCallback<Long>) connection -> {
return connection.incrBy(key.getBytes(), value);
});
}
public Long incr(final String key) {
return redisTemplate.execute((RedisCallback<Long>) connection -> {
return connection.incr(key.getBytes());
});
}
public Long decr(final String key) {
return redisTemplate.execute((RedisCallback<Long>) connection -> {
return connection.decr(key.getBytes());
});
}
public Long decrBy(final String key, long value) {
return redisTemplate.execute((RedisCallback<Long>) connection -> connection.decrBy(key.getBytes(), value));
}
/**
* 分布式锁
*
* @param key
* @param value
* @return
*/
public boolean lock(final String key, final String value) {
//如何不存在key,则可以保存成功
if (setNx(key, value)) {
return true;
}
String currentValue = get(key);
//如果锁过期
if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {//currentValue不为空且小于当前时间
//获取上一个锁的时间value
String oldValue = getSet(key, value);//对应getset,如果key存在
//假设两个线程同时进来这里,因为key被占用了,而且锁过期了。获取的值currentValue=A(get取的旧的值肯定是一样的),两个线程的value都是B,key都是K.锁时间已经过期了。
//而这里面的getAndSet一次只会一个执行,也就是一个执行之后,上一个的value已经变成了B。只有一个线程获取的上一个值会是A,另一个线程拿到的值是B。
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
//oldValue不为空且oldValue等于currentValue,也就是校验是不是上个对应的商品时间戳,也是防止并发
return true;
}
}
return false;
}
/**
* 解锁
*
* @param key
* @param value
*/
public void unlock(String key, String value) {
String currentValue = get(key);
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
del(key);//删除key
}
}
public boolean hSet(String key, Map<String,Object> map, long time){
redisTemplate.opsForHash().putAll(key,map);
expire(key,time);
return true;
}
public Long hIncr(String key, String item,long i){
return redisTemplate.opsForHash().increment(key,item,i);
}
public boolean hHasKey(String key,String item){
return redisTemplate.opsForHash().hasKey(key,item);
}
public Object hGet(String key,String item){
return redisTemplate.opsForHash().get(key,item);
}
public boolean hSet(String key, String item, Object value){
redisTemplate.opsForHash().put(key,item,value);
return true;
}
public boolean hSet(String key, Map<String,Object> map){
redisTemplate.opsForHash().putAll(key,map);
return true;
}
}