新增: redis锁 和 ReentrantLock 工具
This commit is contained in:
12
src/main/java/com/cool/core/annotation/NoRepeatSubmit.java
Normal file
12
src/main/java/com/cool/core/annotation/NoRepeatSubmit.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package com.cool.core.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
public @interface NoRepeatSubmit {
|
||||||
|
long expireTime() default 2000; // 默认2秒过期时间,单位毫秒
|
||||||
|
}
|
||||||
39
src/main/java/com/cool/core/aop/NoRepeatSubmitAspect.java
Normal file
39
src/main/java/com/cool/core/aop/NoRepeatSubmitAspect.java
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package com.cool.core.aop;
|
||||||
|
|
||||||
|
import com.cool.core.annotation.NoRepeatSubmit;
|
||||||
|
import com.cool.core.cache.CoolCache;
|
||||||
|
import com.cool.core.exception.CoolPreconditions;
|
||||||
|
import com.cool.core.util.CoolSecurityUtil;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Objects;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Around;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class NoRepeatSubmitAspect {
|
||||||
|
|
||||||
|
private final CoolCache coolCache;
|
||||||
|
|
||||||
|
@Around("@annotation(noRepeatSubmit)")
|
||||||
|
public Object around(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) throws Throwable {
|
||||||
|
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(
|
||||||
|
RequestContextHolder.getRequestAttributes())).getRequest();
|
||||||
|
String key = request.getRequestURI() + ":" + CoolSecurityUtil.getCurrentUserId();
|
||||||
|
// 加锁
|
||||||
|
CoolPreconditions.check(!coolCache.tryLock(key, Duration.ofMillis(noRepeatSubmit.expireTime())), "请勿重复操作");
|
||||||
|
try {
|
||||||
|
return joinPoint.proceed();
|
||||||
|
} finally {
|
||||||
|
// 移除锁
|
||||||
|
coolCache.unlock(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
85
src/main/java/com/cool/core/cache/CoolCache.java
vendored
85
src/main/java/com/cool/core/cache/CoolCache.java
vendored
@@ -5,7 +5,12 @@ import cn.hutool.core.util.ObjectUtil;
|
|||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.cool.core.util.ConvertUtil;
|
import com.cool.core.util.ConvertUtil;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.autoconfigure.cache.CacheType;
|
import org.springframework.boot.autoconfigure.cache.CacheType;
|
||||||
@@ -41,6 +46,10 @@ public class CoolCache {
|
|||||||
|
|
||||||
final private CacheManager cacheManager;
|
final private CacheManager cacheManager;
|
||||||
|
|
||||||
|
private final Map<String, Lock> lockMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static final String LOCK_PREFIX = "lock:";
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
private void init() {
|
private void init() {
|
||||||
cache = cacheManager.getCache(cacheName);
|
cache = cacheManager.getCache(cacheName);
|
||||||
@@ -57,8 +66,7 @@ public class CoolCache {
|
|||||||
* @param keys 一个或多个key
|
* @param keys 一个或多个key
|
||||||
*/
|
*/
|
||||||
public void del(String... keys) {
|
public void del(String... keys) {
|
||||||
if (type.equalsIgnoreCase(CacheType.CAFFEINE.name()) || type.equalsIgnoreCase(
|
if (type.equalsIgnoreCase(CacheType.CAFFEINE.name())) {
|
||||||
CacheType.JCACHE.name())) {
|
|
||||||
Arrays.stream(keys).forEach(o -> cache.evict(o));
|
Arrays.stream(keys).forEach(o -> cache.evict(o));
|
||||||
}
|
}
|
||||||
if (type.equalsIgnoreCase(CacheType.REDIS.name())) {
|
if (type.equalsIgnoreCase(CacheType.REDIS.name())) {
|
||||||
@@ -80,8 +88,7 @@ public class CoolCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Object getIfNullValue(String key) {
|
private Object getIfNullValue(String key) {
|
||||||
if (type.equalsIgnoreCase(CacheType.CAFFEINE.name()) || type.equalsIgnoreCase(
|
if (type.equalsIgnoreCase(CacheType.CAFFEINE.name())) {
|
||||||
CacheType.JCACHE.name())) {
|
|
||||||
Cache.ValueWrapper valueWrapper = cache.get(key);
|
Cache.ValueWrapper valueWrapper = cache.get(key);
|
||||||
if (valueWrapper != null) {
|
if (valueWrapper != null) {
|
||||||
return valueWrapper.get(); // 获取实际的缓存值
|
return valueWrapper.get(); // 获取实际的缓存值
|
||||||
@@ -145,15 +152,77 @@ public class CoolCache {
|
|||||||
if (ObjUtil.isNull(value)) {
|
if (ObjUtil.isNull(value)) {
|
||||||
value = NULL_VALUE;
|
value = NULL_VALUE;
|
||||||
}
|
}
|
||||||
if (type.equalsIgnoreCase(CacheType.CAFFEINE.name()) || type.equalsIgnoreCase(
|
if (type.equalsIgnoreCase(CacheType.CAFFEINE.name())) {
|
||||||
CacheType.JCACHE.name())) {
|
|
||||||
// 放入缓存
|
// 放入缓存
|
||||||
cache.put(key, value);
|
cache.put(key, value);
|
||||||
}
|
} else if (type.equalsIgnoreCase(CacheType.REDIS.name())) {
|
||||||
if (type.equalsIgnoreCase(CacheType.REDIS.name())) {
|
|
||||||
redisCache.put(cacheName, key.getBytes(), ObjectUtil.serialize(value),
|
redisCache.put(cacheName, key.getBytes(), ObjectUtil.serialize(value),
|
||||||
java.time.Duration.ofSeconds(ttl));
|
java.time.Duration.ofSeconds(ttl));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试获取锁
|
||||||
|
*
|
||||||
|
* @param key 锁的 key
|
||||||
|
* @param expireTime 锁的过期时间
|
||||||
|
* @return 如果成功获取锁则返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
public boolean tryLock(String key, Duration expireTime) {
|
||||||
|
String lockKey = getLockKey(key);
|
||||||
|
if (type.equalsIgnoreCase(CacheType.CAFFEINE.name())) {
|
||||||
|
Lock lock = lockMap.computeIfAbsent(lockKey, k -> new ReentrantLock());
|
||||||
|
return lock.tryLock();
|
||||||
|
}
|
||||||
|
byte[] lockKeyBytes = lockKey.getBytes();
|
||||||
|
// 使用 putIfAbsent 来尝试设置锁,如果成功返回 true,否则返回 false
|
||||||
|
return redisCache.putIfAbsent(cacheName, lockKeyBytes, new byte[0], expireTime) == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 释放锁
|
||||||
|
*/
|
||||||
|
public void unlock(String key) {
|
||||||
|
String lockKey = getLockKey(key);
|
||||||
|
if (type.equalsIgnoreCase(CacheType.CAFFEINE.name())) {
|
||||||
|
Lock lock = lockMap.get(lockKey);
|
||||||
|
if (lock != null && lock.tryLock()) {
|
||||||
|
lock.unlock();
|
||||||
|
lockMap.remove(lockKey);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
redisCache.remove(cacheName, lockKey.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拼接锁前缀
|
||||||
|
*/
|
||||||
|
private String getLockKey(String key) {
|
||||||
|
return LOCK_PREFIX + key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 等待锁
|
||||||
|
*
|
||||||
|
* @param key 锁的 key
|
||||||
|
* @param expireTime 锁的过期时间
|
||||||
|
* @return 如果成功获取锁则返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
public boolean waitForLock(String key, Duration expireTime, Duration waitTime) {
|
||||||
|
long endTime = System.currentTimeMillis() + waitTime.toMillis();
|
||||||
|
while (System.currentTimeMillis() < endTime) {
|
||||||
|
if (tryLock(key, expireTime)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 等待锁释放
|
||||||
|
try {
|
||||||
|
Thread.sleep(100); // 可以根据需要调整等待时间
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ import org.springframework.data.redis.core.RedisTemplate;
|
|||||||
public class RedisConfig {
|
public class RedisConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public RedisTemplate<Object, Object> redisTemplate(
|
public RedisTemplate<String, Object> redisTemplate(
|
||||||
RedisConnectionFactory redisConnectionFactory) {
|
RedisConnectionFactory redisConnectionFactory) {
|
||||||
RedisTemplate<Object, Object> template = new RedisTemplate<>();
|
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||||
template.setConnectionFactory(redisConnectionFactory);
|
template.setConnectionFactory(redisConnectionFactory);
|
||||||
return template;
|
return template;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user