预防缓存穿透工具类

news/2025/1/15 18:04:41/

1. 前言

缓存穿透大家都知道,这里简单过一下

缓存和数据库中都没有的数据,而用户不断发起请求。比如查询id = -1 的值

想着很多面向C端的查询接口,可能都需要做一下缓存操作,这里简单写了个自定义注解,将查询结果(包含null值)做个缓存

这个只能预防单秒内接口高频次请求,要是一直搞随机值请求这个只能采取其他手段处理了(比如IP拉黑什么的…)

工具类留底,以后兴许可以直接抄~( ̄▽ ̄)"

2. 正文

直接上代码了

2.1 自定义注解

CacheResult

import java.lang.annotation.*;/*** <pre>* 接口缓存* 根据接口的第一个入参对象,和返回值进行缓存* 缓存的前缀为 TEMPORARY_CACHE:类名:方法名:key值* 示例:@CacheResult(key="userId + '_' + ecommerceId", seconds = 2L)* 缓存的key:TEMPORARY_CACHE:EcommerceController:getOrderList:407622341504839680_527203683850731520* </pre>* @author weiheng* @date 2023-08-25**/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface CacheResult {/** 入参支持 SpEL表达式 做参数提取,比如入参对象有属性userId和ecommerceId -> key="userId + '_' + ecommerceId" */String key();/** 缓存时长,单位:秒 */long seconds();
}

2.2 统一做缓存处理的切面

CacheAspect

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RBucket;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;import javax.annotation.Resource;/*** 缓存统一处理* @author weiheng* @date 2023-08-25**/
@Slf4j
@Aspect
@Component
public class CacheAspect {/** 临时缓存的统一前缀 */public static final String DEFAULT_PREFIX = "TEMPORARY_CACHE:";/** 缓存分隔符 */public static final String DELIMITER = ":";@Resourceprivate RedissonHelper redissonHelper;/*** 拦截通知** @param proceedingJoinPoint 入参* @return Object*/@Around("@annotation(cacheSeconds)")public Object around(ProceedingJoinPoint proceedingJoinPoint, CacheResult cacheSeconds) {Object[] args = proceedingJoinPoint.getArgs();if (args.length == 0) {// 方法没有入参,不做缓存return proceed(proceedingJoinPoint);}// 1. 判断缓存中是否存在,有则直接返回Object firstArg = args[0];// 获取用户指定的参数ExpressionParser parser = new SpelExpressionParser();EvaluationContext context = new StandardEvaluationContext(firstArg);Expression keyExpression = parser.parseExpression(cacheSeconds.key());String businessKey = keyExpression.getValue(context, String.class);// 拼装缓存keyString className = proceedingJoinPoint.getTarget().getClass().getSimpleName();String methodName = proceedingJoinPoint.getSignature().getName();String prefix = className + DELIMITER + methodName;String cacheKey = DEFAULT_PREFIX + prefix + DELIMITER + businessKey;RBucket<?> bucket = redissonHelper.getBucket(cacheKey);boolean exists = bucket.isExists();if (exists) {// 缓存中有值,直接返回return bucket.get();}// 2. 执行方法体Object returnValue = proceed(proceedingJoinPoint);// 3. 做个N秒的缓存long seconds = cacheSeconds.seconds();redissonHelper.setValueAndSeconds(cacheKey, returnValue, seconds);return returnValue;}private Object proceed(ProceedingJoinPoint proceedingJoinPoint) {Object returnValue;try {returnValue = proceedingJoinPoint.proceed();} catch (Throwable e) {log.error("error msg:", e);if (e instanceof SystemException) {throw (SystemException) e;}throw new SystemException(e.getMessage());}return returnValue;}

3. 使用示例

原本定义个2秒就OK了,这里为了方便看测试结果,给了60秒

@CacheResult(key=“userId + ‘_’ + ecommerceId”, seconds = 60L)

redis缓存如下:
在这里插入图片描述
就到这里了


http://www.ppmy.cn/news/1060288.html

相关文章

portainer初体验

官方文档 安装 docker 这里采用的的是国内汉化的一个镜像&#xff0c;版本号2.16.2。 地址 docker run -d --restartalways --name"portainer" -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock 6053537/portainer-ce体验 访问9000端口。 尝试&#x…

连锁餐饮行业的运维困局,向日葵远程控制提供“标准答案”

企业数字化转型的应用落地&#xff0c;在连锁餐饮行业是非常容易被顾客所感知到的&#xff0c;最典型的例子就是各种自助点餐设备。 往往在这些自助点餐设备的背后&#xff0c;还拥有一个覆盖进销存管理、供应链、客户反馈、巡店管理、视频监控等方面的完善的数字化系统&#…

本地编译angular提示内存溢出

本地遇到编译angular时&#xff0c;报如下错误&#xff1a; FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory 两种解决办法&#xff0c;具体如下&#xff1a; 设置环境变量&#xff0c;见图&#xff1a; 直接在…

网工内推 | 上市公司、国企招网工,五险一金,包吃包住

01 宁波领越智能设备有限公司 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1&#xff1a;负责集团内部网络运维安全管理工作和数据中心管理 2&#xff1a;挖掘和发现目前整体网络运行体系中存在的问题和不足&#xff0c;提出具体改进方案并推进实施。 3&#xff1a;…

管理类联考——英语——实战篇——小作文——信件——第一段

信件 历年小作文体裁 年份考查形式主题2010感谢信(+邀请)感谢同事的热情接待,邀请他来中国旅游2011祝贺信(+建议)祝贺李明考上大学,提出学习建议2012投诉信(+请求)投诉电子字典质量问题,要求解决2013邀请信(+介绍)邀请同学参加义卖会,并介绍活动细节2014告知信(+介…

Linux socket网络编程概述 和 相关API讲解

socket网络编程的步骤 大体上&#xff0c;连接的建立过程就是&#xff1a;服务器在确定协议类型后&#xff0c;向外广播IP地址和端口号&#xff0c;并监听等待&#xff0c;直到客户端获取了IP地址和端口号并成功连接&#xff1a; 使用socket来进行tcp协议的网络编程的大体步骤…

规划 2023 年拥有实体店的 DTC 品牌的发展方向

2023 年&#xff0c;DTC 品牌应谨慎考虑扩张实体店&#xff0c;以此作为扩大影响力和服务客户群的另一种方式。只有当企业在数字领域取得一定程度的成功&#xff0c;并且拥有适当管理数字和实体店的资源时&#xff0c;才应该进行这种扩张。这里的前提是&#xff0c;如果生意不好…

GitLab-CI 指南

GitLab CI 指南 前置工作 部署GitLab 部署GitLab-Runner 注册Runner到GitLab docker exec -it gitlab-runner bash # 进入容器 gitlab-runner register #调用register命令开始注册 # 在Gitlab Setting中找到Runners,如下图所示Enter the GitLab instance URL (for example, …