想要实现用户对接口访问的限制,就要考虑实现功能的两个维度:
- 固定时间、固定访问次数
实现功能的具体方法:
- redis设置key过期时间的方法–>expire key secondsredis设置键的数字值递增的方法–>incr key
那么我们可以用请求用户的ip这一唯一标识作为redis的key,通过increment方法实现访问次数的记录
访问控制注解类(AcessLimit.java)
@Retention(RUNTIME)
@Target(METHOD)//用在方法上的注解
public @interface AccessLimit {
int seconds();
int maxCount();
}
拦截器(MyInterceptor.java)
控制访问接口频率的业务逻辑主要靠拦截器实现
@Slf4j
public class MyInterceptor implements HandlerInterceptor {
@Resource
RedisService redisService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
System.out.println("request=====>"+httpServletRequest);
System.out.println("response=====>"+httpServletResponse);
System.out.println("handler=====>"+handler);
//如果请求输入方法
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
//获取方法中的注解,看是否有该注解
AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
if (accessLimit != null) {
long seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
//关于key的生成规则可以自己定义 本项目需求是对每个方法都加上限流功能,如果你只是针对ip地址限流,那么key只需要只用ip就好
String key = SystemUtil.getClientIp(httpServletRequest) + hm.getMethod().getName();
//从redis中获取用户访问的次数
try {
long q = redisService.incr(key, seconds);//此操作代表获取该key对应的值自增1后的结果
//在限定的时间内访问超过限制次数 报请求频繁错误
if (q > maxCount) {
//加1
render(httpServletResponse, new ResponseMsg(0, "请求过于频繁,请稍候再试", null)); //这里的CodeMsg是一个返回参数
return false;
}
return true;
} catch (RedisConnectionFailureException e) {
log.info("redis错误" + e.getMessage().toString());
return true;
}
}
}
return false;
}
private void render(HttpServletResponse response, ResponseMsg cm) throws Exception {
response.setContentType("application/json;charset=UTF-8");
OutputStream out = response.getOutputStream();
String str = new Gson().toJson(cm);
out.write(str.getBytes("UTF-8"));
out.flush();
out.close();
}
}
拦截器注册(MyWebConfig.java)
当然,有了拦截器还得注册进web中,否则无法生效
@SpringBootConfiguration
public class MyWebConfig implements WebMvcConfigurer {
@Bean
public MyInterceptor getMyInterceptor(){
return new MyInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
}
}
操作redis的服务层(RedisServiceImpl.java)
接口
public interface RedisService {
boolean set(String key, String value);
String get(String key);
boolean expire(String key, long expire);
boolean remove(String key);
Long incr(String key,long time);
}
.
实现
@Service
public class RedisServiceImpl implements RedisService{
@Resource
private RedisTemplate redisTemplate;
@Override
public boolean set(final String key, final String value) {
boolean result = redisTemplate.execute(new RedisCallback() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer serializer = redisTemplate.getStringSerializer();
connection.set(serializer.serialize(key), serializer.serialize(value));
return true;
}
});
return result;
}
@Override
public String get(final String key) {
String result = redisTemplate.execute(new RedisCallback() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer serializer = redisTemplate.getStringSerializer();
byte[] value = connection.get(serializer.serialize(key));
return serializer.deserialize(value);
}
});
return result;
}
@Override
public boolean expire(final String key, long expire) {
return redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
@Override
public boolean remove(final String key) {
boolean result = redisTemplate.execute(new RedisCallback() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer serializer = redisTemplate.getStringSerializer();
connection.del(key.getBytes());
return true;
}
});
return result;
}
@Override
public Long incr(String key,long time){//key:ip+方法 time是时间6秒
redisTemplate.setKeySerializer(new StringRedisSerializer());
long count = redisTemplate.opsForValue().increment(key, 1);
//String o = (String) redisTemplate.opsForValue().get(key);
//System.out.println("自增后的key值" + o);
//count=1表示某段限制时间内的第一次访问
if (count == 1) {
//设置有效期一分钟
set(key,"1");
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return count;
}
}
获取请求用户ip的工具类
@RestController
@RequestMapping
public class LimitTestController {
@RequestMapping("testlimit")
//将限制接口注解加上
@AccessLimit(seconds = 6,maxCount = 2)
public String testLimit(){
System.out.println("进入方法");
return "测试方法";
}
}
最后是获取请求用户ip的工具类
public class SystemUtil {
public static String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknow".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();
if (ip.equals("127.0.0.1")) {
//根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (Exception e) {
e.printStackTrace();
}
ip = inet.getHostAddress();
}
}
if (ip != null && ip.length() > 15) {
if (ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
}
return ip;
}
}
访问测试接口
- 访问http://localhost:8080/testlimit正常返回
. 访问频率超过接口定义



