|
@@ -398,21 +398,34 @@ public AlbumInfo getAlbumInfoFromDB(Long id) {
|
|
|
```java
|
|
|
package com.atguigu.tingshu.common.cache;
|
|
|
|
|
|
+import com.atguigu.tingshu.common.constant.RedisConstant;
|
|
|
+
|
|
|
import java.lang.annotation.*;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
-/**
|
|
|
- * 自定义缓存注解
|
|
|
- */
|
|
|
@Target({ElementType.METHOD})
|
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
|
@Inherited
|
|
|
@Documented
|
|
|
public @interface GuiGuCache {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 存入Redis业务数据以及锁的前缀
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ String prefix() default "";
|
|
|
+
|
|
|
/**
|
|
|
- * 缓存业务及分布式锁key的前缀
|
|
|
+ * 存入Redis缓存业务数据过期时间
|
|
|
* @return
|
|
|
*/
|
|
|
- String prefix() default "data:";
|
|
|
+ long ttl() default RedisConstant.ALBUM_TIMEOUT;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 时间单位
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ TimeUnit unit() default TimeUnit.SECONDS;
|
|
|
}
|
|
|
```
|
|
|
|
|
@@ -425,7 +438,6 @@ package com.atguigu.tingshu.common.cache;
|
|
|
|
|
|
import cn.hutool.core.util.RandomUtil;
|
|
|
import com.atguigu.tingshu.common.constant.RedisConstant;
|
|
|
-import com.atguigu.tingshu.common.login.GuiGuLogin;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.aspectj.lang.ProceedingJoinPoint;
|
|
|
import org.aspectj.lang.annotation.Around;
|
|
@@ -437,87 +449,83 @@ import org.springframework.data.redis.core.RedisTemplate;
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
|
import java.util.Arrays;
|
|
|
-import java.util.List;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
* @author: atguigu
|
|
|
- * @create: 2024-08-17 08:57
|
|
|
+ * @create: 2025-06-10 11:41
|
|
|
*/
|
|
|
@Slf4j
|
|
|
@Aspect
|
|
|
@Component
|
|
|
public class GuiGuCacheAspect {
|
|
|
-
|
|
|
@Autowired
|
|
|
private RedisTemplate redisTemplate;
|
|
|
|
|
|
@Autowired
|
|
|
private RedissonClient redissonClient;
|
|
|
|
|
|
+
|
|
|
/**
|
|
|
- * 自定义缓存注解的切面逻辑
|
|
|
+ * 对所有方法上使用自定义缓存注解方法进行增强
|
|
|
*
|
|
|
- * @param pjp 切入点对象
|
|
|
- * @param guiGuCache 方法使用使用注解对象
|
|
|
+ * @param joinPoint
|
|
|
+ * @param guiGuCache
|
|
|
* @return
|
|
|
* @throws Throwable
|
|
|
*/
|
|
|
@Around("@annotation(guiGuCache)")
|
|
|
- public Object doBasicProfiling(ProceedingJoinPoint pjp, GuiGuCache guiGuCache) throws Throwable {
|
|
|
+ public Object around(ProceedingJoinPoint joinPoint, GuiGuCache guiGuCache) throws Throwable {
|
|
|
try {
|
|
|
- //1.优先从缓存Redis中获取业务数据
|
|
|
- //1.1 构建业务数据key 形式=注解前缀+方法参数
|
|
|
- //1.1.1 获取注解前缀
|
|
|
- String prefix = guiGuCache.prefix();
|
|
|
- //1.1.2 获取方法参数如果存在多个参数采用_拼接
|
|
|
+ //1.优先从缓存中获取业务数据
|
|
|
+ //1.1 将方法参数采用"_"进行拼接作为Key的一部分
|
|
|
String params = "none";
|
|
|
- Object[] args = pjp.getArgs();
|
|
|
+ Object[] args = joinPoint.getArgs();
|
|
|
if (args != null && args.length > 0) {
|
|
|
- List<Object> objects = Arrays.asList(args);
|
|
|
- params = objects.stream()
|
|
|
+ params = Arrays.stream(args)
|
|
|
.map(Object::toString)
|
|
|
.collect(Collectors.joining("_"));
|
|
|
}
|
|
|
- String dataKey = prefix + params;
|
|
|
- //1.2 查询Redis获取业务数据
|
|
|
- Object objectResult = redisTemplate.opsForValue().get(dataKey);
|
|
|
-
|
|
|
- //2.如果命中缓存,直接返回业务数据即可(不需要查库)
|
|
|
- if (objectResult != null) {
|
|
|
- return objectResult;
|
|
|
+ //1.2 构建缓存业务数据Key
|
|
|
+ String dataKey = guiGuCache.prefix() + params;
|
|
|
+ //1.3 查询Redis中缓存数据
|
|
|
+ Object result = redisTemplate.opsForValue().get(dataKey);
|
|
|
+ if (result != null) {
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
- //3.如果未命中缓存,先获取分布式锁
|
|
|
- //3.1 构建分布式锁key
|
|
|
+ //2.获取分布式锁:基于Redisson框架提供分布式锁
|
|
|
+ //2.1 构建锁Key 形式=业务Key+锁后缀
|
|
|
String lockKey = dataKey + RedisConstant.CACHE_LOCK_SUFFIX;
|
|
|
- //3.2 获取锁对象
|
|
|
+ //2.2 基于RedissonClient对象创建锁对象
|
|
|
RLock lock = redissonClient.getLock(lockKey);
|
|
|
- //3.3 获取分布式锁
|
|
|
- boolean flag = lock.tryLock();
|
|
|
|
|
|
- //4.获取分布锁锁成功,执行查询数据库(目标方法执行),并设置缓存
|
|
|
+ //2.3 尝试获取分布式锁
|
|
|
+ boolean flag = lock.tryLock(1, TimeUnit.SECONDS);
|
|
|
+
|
|
|
+ //3.获取锁成功,执行查库业务方法(目标方法)
|
|
|
if (flag) {
|
|
|
try {
|
|
|
- //4.1 执行目标方法(执行自定义注解修饰方法-查询数据库方法)
|
|
|
- objectResult = pjp.proceed();
|
|
|
- //4.2 将查询数据库业务数据放入Redis缓存中
|
|
|
- int ttl = RandomUtil.randomInt(100, 600);
|
|
|
- redisTemplate.opsForValue().set(dataKey, objectResult, RedisConstant.ALBUM_TIMEOUT + ttl, TimeUnit.SECONDS);
|
|
|
- return objectResult;
|
|
|
+ //3.1 执行目标查询数据库方法
|
|
|
+ result = joinPoint.proceed();
|
|
|
+ //3.2 将查询结果放入Redis中
|
|
|
+ long ttl = guiGuCache.ttl() + RandomUtil.randomInt(1200);
|
|
|
+ redisTemplate.opsForValue().set(dataKey, result, ttl, guiGuCache.unit());
|
|
|
+ return result;
|
|
|
} finally {
|
|
|
- //5.释放分布式锁
|
|
|
+ //3.3 释放锁
|
|
|
lock.unlock();
|
|
|
}
|
|
|
} else {
|
|
|
- //6.获取分布式锁失败,进行自旋(自旋可能获取锁成功线程会将业务数据已经放入缓存)
|
|
|
- return this.doBasicProfiling(pjp, guiGuCache);
|
|
|
+ //4.获取锁失败,自旋,可能就会命中缓存
|
|
|
+ Thread.sleep(500);
|
|
|
+ return this.around(joinPoint, guiGuCache);
|
|
|
}
|
|
|
} catch (Throwable e) {
|
|
|
- //7.兜底处理-直接查询数据库
|
|
|
- log.error("[自定义缓存切面]Redis服务不可用,执行兜底方案,查库。异常:{}", e);
|
|
|
- return pjp.proceed();
|
|
|
+ //5.兜底处理:如果Redis服务不可用,则执行目标方法
|
|
|
+ log.error("Redis服务异常:{}", e.getMessage());
|
|
|
+ return joinPoint.proceed();
|
|
|
}
|
|
|
}
|
|
|
}
|