From 43347ba9421ae0466369a8784712c7851aca9b56 Mon Sep 17 00:00:00 2001 From: ruying408 <1877972603@qq.com> Date: Sun, 25 Aug 2024 13:52:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20redis=E9=94=81=20?= =?UTF-8?q?=E5=92=8C=20ReentrantLock=20=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cool/core/annotation/NoRepeatSubmit.java | 12 +++ .../cool/core/aop/NoRepeatSubmitAspect.java | 39 +++++++++ .../java/com/cool/core/cache/CoolCache.java | 85 +++++++++++++++++-- .../cool/core/config/cache/RedisConfig.java | 4 +- 4 files changed, 130 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/cool/core/annotation/NoRepeatSubmit.java create mode 100644 src/main/java/com/cool/core/aop/NoRepeatSubmitAspect.java diff --git a/src/main/java/com/cool/core/annotation/NoRepeatSubmit.java b/src/main/java/com/cool/core/annotation/NoRepeatSubmit.java new file mode 100644 index 0000000..818c473 --- /dev/null +++ b/src/main/java/com/cool/core/annotation/NoRepeatSubmit.java @@ -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秒过期时间,单位毫秒 +} diff --git a/src/main/java/com/cool/core/aop/NoRepeatSubmitAspect.java b/src/main/java/com/cool/core/aop/NoRepeatSubmitAspect.java new file mode 100644 index 0000000..d71d3dc --- /dev/null +++ b/src/main/java/com/cool/core/aop/NoRepeatSubmitAspect.java @@ -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); + } + } +} diff --git a/src/main/java/com/cool/core/cache/CoolCache.java b/src/main/java/com/cool/core/cache/CoolCache.java index fc827a9..38ef40b 100644 --- a/src/main/java/com/cool/core/cache/CoolCache.java +++ b/src/main/java/com/cool/core/cache/CoolCache.java @@ -5,7 +5,12 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.json.JSONUtil; import com.cool.core.util.ConvertUtil; import jakarta.annotation.PostConstruct; +import java.time.Duration; 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 org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.cache.CacheType; @@ -41,6 +46,10 @@ public class CoolCache { final private CacheManager cacheManager; + private final Map lockMap = new ConcurrentHashMap<>(); + + private static final String LOCK_PREFIX = "lock:"; + @PostConstruct private void init() { cache = cacheManager.getCache(cacheName); @@ -57,8 +66,7 @@ public class CoolCache { * @param keys 一个或多个key */ public void del(String... keys) { - if (type.equalsIgnoreCase(CacheType.CAFFEINE.name()) || type.equalsIgnoreCase( - CacheType.JCACHE.name())) { + if (type.equalsIgnoreCase(CacheType.CAFFEINE.name())) { Arrays.stream(keys).forEach(o -> cache.evict(o)); } if (type.equalsIgnoreCase(CacheType.REDIS.name())) { @@ -80,8 +88,7 @@ public class CoolCache { } private Object getIfNullValue(String key) { - if (type.equalsIgnoreCase(CacheType.CAFFEINE.name()) || type.equalsIgnoreCase( - CacheType.JCACHE.name())) { + if (type.equalsIgnoreCase(CacheType.CAFFEINE.name())) { Cache.ValueWrapper valueWrapper = cache.get(key); if (valueWrapper != null) { return valueWrapper.get(); // 获取实际的缓存值 @@ -145,15 +152,77 @@ public class CoolCache { if (ObjUtil.isNull(value)) { value = NULL_VALUE; } - if (type.equalsIgnoreCase(CacheType.CAFFEINE.name()) || type.equalsIgnoreCase( - CacheType.JCACHE.name())) { + if (type.equalsIgnoreCase(CacheType.CAFFEINE.name())) { // 放入缓存 cache.put(key, value); - } - if (type.equalsIgnoreCase(CacheType.REDIS.name())) { + } else if (type.equalsIgnoreCase(CacheType.REDIS.name())) { redisCache.put(cacheName, key.getBytes(), ObjectUtil.serialize(value), 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; + } } diff --git a/src/main/java/com/cool/core/config/cache/RedisConfig.java b/src/main/java/com/cool/core/config/cache/RedisConfig.java index b892683..9da37da 100644 --- a/src/main/java/com/cool/core/config/cache/RedisConfig.java +++ b/src/main/java/com/cool/core/config/cache/RedisConfig.java @@ -15,9 +15,9 @@ import org.springframework.data.redis.core.RedisTemplate; public class RedisConfig { @Bean - public RedisTemplate redisTemplate( + public RedisTemplate redisTemplate( RedisConnectionFactory redisConnectionFactory) { - RedisTemplate template = new RedisTemplate<>(); + RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; }