網(wǎng)站首頁 編程語言 正文
需求
公司里使用OneByOne的方式刪除數(shù)據(jù),為了防止一段時間內(nèi)刪除數(shù)據(jù)過多,讓我這邊做一個接口限流,超過一定閾值后報異常,終止刪除操作。
實現(xiàn)方式
創(chuàng)建自定義注解
@limit
讓使用者在需要的地方配置count(一定時間內(nèi)最多訪問次數(shù))
、period(給定的時間范圍)
,也就是訪問頻率。然后通過LimitInterceptor
攔截方法的請求, 通過 redis+lua 腳本的方式,控制訪問頻率。
源碼
Limit 注解
用于配置方法的訪問頻率count、period
import javax.validation.constraints.Min; import java.lang.annotation.*; @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Limit { /** * key */ String key() default ""; /** * Key的前綴 */ String prefix() default ""; /** * 一定時間內(nèi)最多訪問次數(shù) */ @Min(1) int count(); /** * 給定的時間范圍 單位(秒) */ @Min(1) int period(); /** * 限流的類型(用戶自定義key或者請求ip) */ LimitType limitType() default LimitType.CUSTOMER; }
LimitKey
用于標(biāo)記參數(shù),作為redis key值的一部分
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface LimitKey { }
LimitType
枚舉,redis key值的類型,支持自定義key和ip、methodName中獲取key
public enum LimitType { /** * 自定義key */ CUSTOMER, /** * 請求者IP */ IP, /** * 方法名稱 */ METHOD_NAME; }
RedisLimiterHelper
初始化一個限流用到的redisTemplate Bean
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.io.Serializable; @Configuration public class RedisLimiterHelper { @Bean public RedisTemplate<String, Serializable> limitRedisTemplate(@Qualifier("defaultStringRedisTemplate") StringRedisTemplate redisTemplate) { RedisTemplate<String, Serializable> template = new RedisTemplate<String, Serializable>(); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); template.setConnectionFactory(redisTemplate.getConnectionFactory()); return template; } }
LimitInterceptor
使用 aop 的方式來攔截請求,控制訪問頻率
import com.google.common.collect.ImmutableList; import com.yxt.qida.api.bean.service.xxv2.openapi.anno.Limit; import com.yxt.qida.api.bean.service.xxv2.openapi.anno.LimitKey; import com.yxt.qida.api.bean.service.xxv2.openapi.anno.LimitType; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; 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.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @Slf4j @Aspect @Configuration public class LimitInterceptor { private static final String UNKNOWN = "unknown"; private final RedisTemplate<String, Serializable> limitRedisTemplate; @Autowired public LimitInterceptor(RedisTemplate<String, Serializable> limitRedisTemplate) { this.limitRedisTemplate = limitRedisTemplate; } @Around("execution(public * *(..)) && @annotation(com.yxt.qida.api.bean.service.xxv2.openapi.anno.Limit)") public Object interceptor(ProceedingJoinPoint pjp) { MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); Limit limitAnnotation = method.getAnnotation(Limit.class); LimitType limitType = limitAnnotation.limitType(); int limitPeriod = limitAnnotation.period(); int limitCount = limitAnnotation.count(); /** * 根據(jù)限流類型獲取不同的key ,如果不傳我們會以方法名作為key */ String key; switch (limitType) { case IP: key = getIpAddress(); break; case CUSTOMER: key = limitAnnotation.key(); break; case METHOD_NAME: String methodName = method.getName(); key = StringUtils.upperCase(methodName); break; default: throw new RuntimeException("limitInterceptor - 無效的枚舉值"); } /** * 獲取注解標(biāo)注的 key,這個是優(yōu)先級最高的,會覆蓋前面的 key 值 */ Object[] args = pjp.getArgs(); Annotation[][] paramAnnoAry = method.getParameterAnnotations(); for (Annotation[] item : paramAnnoAry) { int paramIndex = ArrayUtils.indexOf(paramAnnoAry, item); for (Annotation anno : item) { if (anno instanceof LimitKey) { Object arg = args[paramIndex]; if (arg instanceof String && StringUtils.isNotBlank((String) arg)) { key = (String) arg; break; } } } } if (StringUtils.isBlank(key)) { throw new RuntimeException("limitInterceptor - key值不能為空"); } String prefix = limitAnnotation.prefix(); String[] keyAry = StringUtils.isBlank(prefix) ? new String[]{"limit", key} : new String[]{"limit", prefix, key}; ImmutableList<String> keys = ImmutableList.of(StringUtils.join(keyAry, "-")); try { String luaScript = buildLuaScript(); RedisScript<Number> redisScript = new DefaultRedisScript<Number>(luaScript, Number.class); Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, limitPeriod); if (count != null && count.intValue() <= limitCount) { return pjp.proceed(); } else { String classPath = method.getDeclaringClass().getName() + "." + method.getName(); throw new RuntimeException("limitInterceptor - 限流被觸發(fā):" + "class:" + classPath + ", keys:" + keys + ", limitcount:" + limitCount + ", limitPeriod:" + limitPeriod + "s"); } } catch (Throwable e) { if (e instanceof RuntimeException) { throw new RuntimeException(e.getLocalizedMessage()); } throw new RuntimeException("limitInterceptor - 限流服務(wù)異常"); } } /** * lua 腳本,為了保證執(zhí)行 redis 命令的原子性 */ public String buildLuaScript() { StringBuilder lua = new StringBuilder(); lua.append("local c"); lua.append("\nc = redis.call('get',KEYS[1])"); // 調(diào)用不超過最大值,則直接返回 lua.append("\nif c and tonumber(c) > tonumber(ARGV[1]) then"); lua.append("\nreturn c;"); lua.append("\nend"); // 執(zhí)行計算器自加 lua.append("\nc = redis.call('incr',KEYS[1])"); lua.append("\nif tonumber(c) == 1 then"); // 從第一次調(diào)用開始限流,設(shè)置對應(yīng)鍵值的過期 lua.append("\nredis.call('expire',KEYS[1],ARGV[2])"); lua.append("\nend"); lua.append("\nreturn c;"); return lua.toString(); } public String getIpAddress() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } }
TestService
使用方式示例
@Limit(period = 10, count = 10) public String delUserByUrlTest(@LimitKey String token, String thirdId, String url) throws IOException { return "success"; }
原文鏈接:https://juejin.cn/post/7091220805853872158
相關(guān)推薦
- 2022-06-11 .Net項目在Docker容器中開發(fā)部署_實用技巧
- 2022-07-17 Python中使用tkFileDialog實現(xiàn)文件選擇、保存和路徑選擇_python
- 2022-05-06 如何利用Python處理excel表格中的數(shù)據(jù)_python
- 2024-03-15 Spring Framework對DAO(Data Access Object)的支持
- 2021-11-05 VBA工程加密PJ方式(兩種)_VBA
- 2022-09-05 docker-compose啟動redis集群的實現(xiàn)步驟_docker
- 2022-10-11 TCP/IP協(xié)議類比生活案例
- 2022-10-09 淺談C++中各種不同意義的new和delete的使用_C 語言
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支