提示:SpringBoot+redis+aop处理黑白名单
文章目录
目录
文章目录
1.导包
2.配置文件
3.代码
1.返回类型
2.redis
3.redisUtils
4.controller
5.AOP
6.具体实现
4.APIFox压力测试
1.导包
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--切面--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version></dependency></dependencies>
2.配置文件
server:port: 8888spring:redis:host: 127.0.0.1port: 6379database: 6timeout: 2000jedis:pool:max-wait: -1max-idle: 10min-idle: 0
3.代码
1.返回类型
package com.xinggui.api_black_white.domain;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResponseVo<V> {private Integer code;private String msg;private V data;}
2.redis
package com.xinggui.api_black_white.config;import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;public class RedisConfig<V> {@Beanpublic RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);template.setValueSerializer(serializer);template.setKeySerializer(new StringRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(serializer);template.afterPropertiesSet();return template;}
}
3.redisUtils
package com.xinggui.api_black_white.utils;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;import javax.annotation.Resource;
import java.util.Collection;
import java.util.concurrent.TimeUnit;@Component("redisUtils")
public class RedisUtils<V> {@Resourceprivate RedisTemplate<String, V> redisTemplate;private static final Logger logger = LoggerFactory.getLogger(RedisUtils.class);/*** 删除缓存** @param key 可以传一个值 或多个*/public void delete(String... key) {if (key != null && key.length > 0) {if (key.length == 1) {redisTemplate.delete(key[0]);} else {redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));}}}public V get(String key) {return key == null ? null : redisTemplate.opsForValue().get(key);}/*** 普通缓存放入** @param key 键* @param value 值* @return true成功 false失败*/public boolean set(String key, V value) {try {redisTemplate.opsForValue().set(key, value);return true;} catch (Exception e) {logger.error("设置redisKey:{},value:{}失败", key, value);return false;}}/*** 普通缓存放入并设置时间** @param key 键* @param value 值* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期* @return true成功 false 失败*/public boolean setex(String key, V value, long time) {try {if (time > 0) {redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);} else {set(key, value);}return true;} catch (Exception e) {logger.error("设置redisKey:{},value:{}失败", key, value, e);return false;}}
}
4.controller
package com.xinggui.api_black_white.controller;import com.xinggui.api_black_white.annotation.RateLimit;
import com.xinggui.api_black_white.domain.ResponseVo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class TestController {@GetMapping("/test")@RateLimit(enable=true)public ResponseVo<String> test(){return new ResponseVo(200,"success","欢迎来到中国山西");}
}
5.AOP
package com.xinggui.api_black_white.annotation;import java.lang.annotation.*;@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {/*** 获取重试机制的启用状态,默认为不启用(false)* * @return boolean - 是否启用重试机制*/boolean enable() default false;/*** 获取重试次数的限制,默认允许的最大重试次数为3次* * @return int - 最大重试次数限制*/int limit() default 3;/*** 获取时间窗口的长度,默认为1000毫秒* 时间窗口用于定义在多久的时间范围内判断是否达到了重试次数的阈值* * @return long - 时间窗口的长度(以毫秒为单位)*/long timeWindow() default 1000;}
6.具体实现
package com.xinggui.api_black_white.aspect;import com.xinggui.api_black_white.annotation.RateLimit;
import com.xinggui.api_black_white.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;@Slf4j
@Aspect
@Component("rateLimitAspect")
public class RateLimitAspect {@Resourceprivate RedisUtils redisUtils;@Resourceprivate HttpServletRequest request;/*** 切面方法,用于实现速率限制(Rate Limiting)功能* 该方法围绕在标注了@RateLimit注解的方法执行前后,用于控制方法的调用频率* * @param joinPoint 切入点对象,表示当前正在执行的方法* @param rateLimit 注解对象,包含速率限制的参数* @return 方法执行的结果对象* @throws Throwable 如果方法执行过程中发生异常,则抛出Throwable*/@Around("@annotation(rateLimit)")public Object rateLimit(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {// 获取速率限制的次数int limit = rateLimit.limit();// 获取客户端IP地址,用于区分不同的访问者String ip = request.getRemoteAddr();// 构造Redis中的键,用于记录当前方法的访问次数String key = "rate_limit:" + ip + ":" + joinPoint.getSignature().getName();// 从Redis中获取当前方法的访问次数Integer count = (Integer) redisUtils.get(key);// 如果当前方法的访问次数不存在或为0,则重置访问次数为1,并在3秒后过期if (count == null) {redisUtils.setex(key, 1, 3);} else if (count < limit) {// 如果当前访问次数小于限制次数,则增加访问次数,并在3秒后过期redisUtils.setex(key, count + 1, 3);} else {// 如果当前访问次数达到限制次数,则抛出异常,阻止方法的进一步执行throw new RuntimeException("请求过于频繁,请稍后再试");}// 执行方法,并返回结果return joinPoint.proceed();}}