Spring Boot 使用 AOP 防止重复提交
https://www.toutiao.com/i6690413456086008331/
https://mp.weixin.qq.com/s?__biz=MzA5MTMyNTI0Nw==&mid=2649789979&idx=1&sn=1d6c053e736fb645305fff45b46ab0fc&chksm=887a204bbf0da95d1df8c5e508ec193211ee9b8d1123440a892628b2e969b569e0e3e09aa2a8&mpshare=1&scene=1&srcid=&pass_ticket=Tmlt2ow7POhLTEni%2F6xk34UgBXtGusNDwDRk7lDResKGN33%2F2bkn2XS39RkCEN%2FJ#rd
https://blog.youkuaiyun.com/u010096717/article/details/84562926 此链接也可参考
以上两个链接是思路的源头:
验证分布式锁是否可用的代码及分布式锁的实现(验证可用的链接,网上大部分代码不可用)
1.网上给的RedisTemplate+lua暂时未验证成
2.以下方式可行https://www.jianshu.com/p/9b5bc060d01b
public String tryLock(String name, long expire) {
Assert.notNull(name, "Lock name must not be null");
Assert.isTrue(expire>0, "expire must greater than 0");
String token = UUID.randomUUID().toString();
boolean result = redisTemplate.opsForValue().setIfAbsent(name, token);
if(result) {//成功则设置ttl
redisTemplate.expire(name, expire, TimeUnit.MILLISECONDS);
return token;
}else { //失败则检查一下锁有没有设置ttl,没有则设置上
long milliSec = redisTemplate.getExpire(name, TimeUnit.MILLISECONDS);
if (milliSec <= 0) {
redisTemplate.expire(name, expire, TimeUnit.MILLISECONDS);
}
return null;
}
}
https://www.jianshu.com/p/6dd656e7051f
private static String LOCK_PREFIX = "prefix";
public boolean lock(String key) {
String lock = LOCK_PREFIX + key;
return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
if (acquire) {
return true;
} else {
byte[] value = connection.get(lock.getBytes());
if (Objects.nonNull(value) && value.length > 0) {
long expireTime = Long.parseLong(new String(value));
if (expireTime < System.currentTimeMillis()) {
byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE + 1).getBytes());
return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
}
}
}
return false;
});
}
验证代码1
package com.yan.test.redisToken;
import com.yan.annotation.NoRepeatSubmit;
import com.yan.utils.ResultUtils;
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.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @Description:
* @Auther: Administrator
* @Date: 2019/7/6
*/
@Aspect
@Component
public class RepeatSubmitAspect {
private final static Logger logger = LoggerFactory.getLogger(RepeatSubmitAspect.class);
@Autowired
private RedisTemplate redisTemplate;
@Pointcut("@annotation(noRepeatSubmit)")
public void pointCut(NoRepeatSubmit noRepeatSubmit) {
}
@Around("pointCut(noRepeatSubmit)")
public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
int lockSeconds = noRepeatSubmit.lockTime();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Assert.notNull(request, "request can not null");
// 此处可以用token或者JSessionId
String token = request.getHeader("Authorization");
String path = request.getServletPath();
String key = getKey("token", path);
String clientId = getClientId();
boolean isSuccess = tryLock(key, clientId, lockSeconds);
if (isSuccess) {
logger.info("tryLock success, key = [{}], clientId = [{}] lockSeconds=[{}]", key, clientId, lockSeconds);
// 获取锁成功, 执行进程
Object result;
try {
result = pjp.proceed();
} finally {
// 解锁
releaseLock(key, clientId);
logger.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
}
return result;
} else {
// 获取锁失败,认为是重复提交的请求
logger.info("tryLock fail, key = [{}]", key);
return ResultUtils.error().toString();// ResultBean(ResultBean.FAIL, "重复请求,请稍后再试", null);
}
}
private String getKey(String token, String path) {
return token + path;
}
private String getClientId() {
return UUID.randomUUID().toString();
}
public boolean tryLock(String key, String value, long time) {
// try {
//// redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
// if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
// //这里存在宕机风险,导致设置有效期失败
// redisTemplate.expire(key, time, TimeUnit.SECONDS);
// System.out.println("-----------------这里存在宕机风险");
// }
// Object obj = redisTemplate.opsForValue().get(key);
// if (ObjectUtils.isEmpty(obj)) {
// return false;
// }
// } catch (Exception e) {
// logger.error("tryLock error", e);
// return false;
// }
// return true;
//https://www.jianshu.com/p/9b5bc060d01b
boolean result = redisTemplate.opsForValue().setIfAbsent(key, value);
if (result) {//成功则设置ttl
redisTemplate.expire(key, time, TimeUnit.SECONDS);
return true;
} else { //失败则检查一下锁有没有设置ttl,没有则设置上
long milliSec = redisTemplate.getExpire(key, TimeUnit.SECONDS);
if (milliSec <= 0) {
redisTemplate.expire(key, time, TimeUnit.MILLISECONDS);
}
return false;
}
//https://www.jianshu.com/p/6dd656e7051f
// return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
// String lock = "redis-lock-1";
// long expireAt = System.currentTimeMillis() + 50 * 1000 + 1;
// Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
//
//
// if (acquire) {
// return true;
// } else {
//
// byte[] value1 = connection.get(lock.getBytes());
//
// if (Objects.nonNull(value1) && value1.length > 0) {
//
// long expireTime = Long.parseLong(new String(value1));
//
// if (expireTime < System.currentTimeMillis()) {
// byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(expireAt).getBytes());
//
// return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
// }
// }
// }
// return false;
// });
}
void releaseLock(String key, String value) {
Object obj = redisTemplate.opsForValue().get(key);
if (ObjectUtils.isEmpty(obj)) {
return;
}
redisTemplate.delete(key);
}
// private static final Long SUCCESS = 1L;
//
//
// /**
// * 获取锁
// *
// * @param lockKey
// * @param value
// * @param expireTime:单位-秒
// * @return
// */
//
// public boolean tryLock(String lockKey, String value, int expireTime) {
// boolean ret = false;
// try {
// String script = "if redis.call('setNx',KEYS[1],ARGV[1]) then if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end end";
// RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
// Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value, expireTime);
// if (SUCCESS.equals(result)) {
// return true;
// }
// } catch (Exception e) {
// return ret;
// }
// return ret;
// }
//
//
// /**
// * 释放锁
// *
// * @param lockKey
// * @param value
// * @return
// */
// public boolean releaseLock(String lockKey, String value) {
// String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
// Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value);
// if (SUCCESS.equals(result)) {
// return true;
// }
// return false;
// }
}
多线程验证代码2
package com.yan.test.redisToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Description:
* @Auther: Administrator
* @Date: 2019/7/6
*/
@Component
public class RunTest implements ApplicationRunner {
private static final Logger logger = LoggerFactory.getLogger(RunTest.class);
@Autowired
private RestTemplate restTemplate;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("执行多线程测试");
String url = "http://localhost:8090/getIndex1";
CountDownLatch countDownLatch = new CountDownLatch(1);
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
String userId = "userId" + i;
HttpEntity request = buildRequest(userId);
executorService.submit(() -> {
try {
//countDownLatch.await();
System.out.println("Thread:" + Thread.currentThread().getName() + ", time:" + System.currentTimeMillis());
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class, "");
System.out.println("Thread:" + Thread.currentThread().getName() + ", body=" + response.getBody());
} catch (Exception e) {
logger.error("ApplicationRunner run fail", e);
}
});
}
countDownLatch.countDown();
}
private HttpEntity buildRequest(String userId) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "yourToken");
Map<String, Object> body = new HashMap<>();
body.put("userId", userId);
return new HttpEntity<>(body, headers);
}
}