您的位置:首页 > 科技 > 能源 > 建设企业和施工企业_一个人在线观看免费中文_百度seo优化系统_拉新推广渠道

建设企业和施工企业_一个人在线观看免费中文_百度seo优化系统_拉新推广渠道

2025/3/9 6:58:17 来源:https://blog.csdn.net/qq_44820964/article/details/146122190  浏览:    关键词:建设企业和施工企业_一个人在线观看免费中文_百度seo优化系统_拉新推广渠道
建设企业和施工企业_一个人在线观看免费中文_百度seo优化系统_拉新推广渠道

某业务中需要对后端接口进行限流,我们可以直接引入阿里巴巴的Sentinel快速实现,但是某企业中出于安全考虑,需要部门自己研发一套,可以采用Redis+Lua脚本+AOP+反射+自定义注解来实现
思路来源于链接

项目结构:
在这里插入图片描述
启动类:不需要额外加东西,默认即可
在这里插入图片描述
pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.4.3</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>org.example</groupId><artifactId>my-rate-limit</artifactId><version>0.0.1-SNAPSHOT</version><name>my-rate-limit</name><description>my-rate-limit</description><url/><licenses><license/></licenses><developers><developer/></developers><scm><connection/><developerConnection/><tag/><url/></scm><properties><java.version>21</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-redis</artifactId><version>3.1.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.22.1</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

application.properties:配置redis相关

spring.application.name=my-rate-limit
server.port=8081
# redis
spring.data.redis.database=0
spring.data.redis.host=127.0.0.1
spring.data.redis.port=6379
spring.data.redis.password=
spring.data.redis.lettuce.pool.max-active=8
spring.data.redis.lettuce.pool.max-wait=-1ms
spring.data.redis.lettuce.pool.max-idle=8
spring.data.redis.lettuce.pool.min-idle=0

RedisConfig:常规的序列化配置

package org.example.myratelimit.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** RedisConfig* @author xiajunfeng* @date 2025/03/08*/
@Configuration
@EnableAspectJAutoProxy //V2  开启AOP自动代理
public class RedisConfig
{/*** @param lettuceConnectionFactory* @return** redis序列化的工具配置类,下面这个请一定开启配置* 127.0.0.1:6379> keys ** 1) "ord:102"  序列化过* 2) "\xac\xed\x00\x05t\x00\aord:102"   野生,没有序列化过*/@Beanpublic RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory){RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(lettuceConnectionFactory);//设置key序列化方式stringredisTemplate.setKeySerializer(new StringRedisSerializer());//设置value的序列化方式jsonredisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.afterPropertiesSet();return redisTemplate;}
}

以上都为常规配置,配置完成后,开始写接口

RedisLimitController:接口中用到了自定义注解**@RedisLimitAnnotation**

package org.example.myratelimit.controller;import lombok.extern.slf4j.Slf4j;
import org.example.myratelimit.annotation.RedisLimitAnnotation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** RedisLimitController* @author xiajunfeng* @date 2025/03/08*/
@Slf4j
@RestController
public class RedisLimitController {/*** 10秒内最多访问3次* @return*/@GetMapping("/redis/limit/test")@RedisLimitAnnotation(key = "redisLimit", permitsPerSecond = 3, expire = 10, msg = "当前访问人数较多,请稍后再试,自定义提示!")public String redisLimit(){return "正常业务返回,订单流水:xxxx" ;}}

RedisLimitAnnotation:自定义注解,定义了key、最大访问次数、过期窗口时间、限流出现提示语

package org.example.myratelimit.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author xiajunfeng*/@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface RedisLimitAnnotation {/*** 资源的key,唯一* 作用:不同的接口,不同的流量控制*/String key() default "";/*** 最多的访问限制次数*/long permitsPerSecond() default 3;/*** 过期时间(计算窗口时间),单位秒默认30*/long expire() default 30;/*** 默认温馨提示语*/String msg() default "default message:系统繁忙or你点击太快,请稍后再试,谢谢";
}

RedisLimitAop:使用AOP技术实现关键限流逻辑,调用了lua脚本

package org.example.myratelimit.aop;import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
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.example.myratelimit.annotation.RedisLimitAnnotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;/*** RedisLimitAop* @author xiajunfeng* @date 2025/03/08*/
@Slf4j
@Aspect
@Component
public class RedisLimitAop {Object result = null;@Resourceprivate StringRedisTemplate stringRedisTemplate;/***  redis调用lua脚本*/private DefaultRedisScript<Long> redisLuaScript;@PostConstructpublic void init(){redisLuaScript = new DefaultRedisScript<>();redisLuaScript.setResultType(Long.class);redisLuaScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("rateLimiter.lua")));}@Around("@annotation(org.example.myratelimit.annotation.RedisLimitAnnotation)")public Object around(ProceedingJoinPoint joinPoint){MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();RedisLimitAnnotation redisLimitAnnotation = method.getAnnotation(RedisLimitAnnotation.class);// redis keyString key = redisLimitAnnotation.key();// @RedisLimitAnnotation中的key不能为空if(key == null){throw new RuntimeException("请在注解属性中定义key");}// 最多的访问限制次数long limit = redisLimitAnnotation.permitsPerSecond();// 过期时间(计算窗口时间)long expire = redisLimitAnnotation.expire();// redis key 数组List<String> keys = new ArrayList<>();keys.add(key);// 执行lua脚本后的返回结果Long count = stringRedisTemplate.execute(redisLuaScript,keys,String.valueOf(limit),String.valueOf(expire));// 如果结果为0,证明已经成功运行限流功能if (count == 0) {System.out.println("启动了限流功能,key: "+key);// 返回@RedisLimitAnnotation中自定义的msg内容return redisLimitAnnotation.msg();}try {//放行result = joinPoint.proceed();} catch (Throwable e) {throw new RuntimeException(e);}return result;}}

rateLimiter.lua:

--获取KEY,针对那个接口进行限流,Lua脚本中的数组索引默认是从1开始的而不是从零开始。
local key = KEYS[1]
--获取注解上标注的限流次数
local limit = tonumber(ARGV[1])local curentLimit = tonumber(redis.call('get', key) or "0")--超过限流次数直接返回零,否则再走else分支
if curentLimit + 1 > limit
then return 0
-- 首次直接进入
else-- 自增长 1redis.call('INCRBY', key, 1)-- 设置过期时间redis.call('EXPIRE', key, ARGV[2])return curentLimit + 1
end--@RedisLimitAnnotation(key = "redisLimit", permitsPerSecond = 2, expire = 1, msg = "当前排队人数较多,请稍后再试!")

编写完成后,启动服务,访问http://localhost:8081/redis/limit/test,我的自定义注解配置为@RedisLimitAnnotation(key = “redisLimit”, permitsPerSecond = 3, expire = 10, msg = “当前访问人数较多,请稍后再试,自定义提示!”)
即10秒内最多访问3次,浏览器快速访问这个接口三次,浏览器出现错误提示,成功!
在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com