1.如何对同一IP访问同一接口进行频率限制
话不多说,直接开干,首先写一个注解类
import java.lang.annotation.*;
/**
* 接口限流
* @author rs
*
*/
@Inherited
@Documented
@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface VisitLimit {
//标识 指定sec时间段内的访问次数限制
int limit() default 5;
//标识 时间段
long sec() default 5;
}
使用注解的原因是:我们使用拦截器在请求处理之前,检查某个请求接口是否有该注解,如果有该注解,获取访问次数和时间段(比如:在1s中只能访问一次)。接下来我们就来写一个拦截器
import org.test.annotation.VisitLimit;
import org.test.exception.BusinessException;
import org.test.redis.RedisCache;
import org.test.service.redis.RedisService;
import org.test.util.IPUtils;
import org.test.util.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
@Component
public class VisitLimitInterceptor extends HandlerInterceptorAdapter {
@Autowired
private RedisUtils redisService;
/**
* 处理请求之前被调用
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
if (!method.isAnnotationPresent(VisitLimit.class)) {
return true;
}
VisitLimit accessLimit = method.getAnnotation(VisitLimit.class);
if (accessLimit == null) {
return true;
}
int limit = accessLimit.limit();
long sec = accessLimit.sec();
String key = IPUtils.getIpAddr(request) + request.getRequestURI();
Integer maxLimit =null;
Object value =redisService.get(key);
if(value!=null && !value.equals("")) {
maxLimit = Integer.valueOf(String.valueOf(value));
}
if (maxLimit == null) {
redisService.set(key, "1", sec);
} else if (maxLimit < limit) {
Integer i = maxLimit+1;
redisService.set(key, i.toString(), sec);
} else {
// output(response, "请求太频繁!");
// return false;
throw new BusinessException(500,"请求太频繁!");
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
这里用到了redis,解释一下redis的key(IP+URL)记录了某个ip访问某个接口,value存的是访问的次数,加上一个过期时间,过期时间就是我们在注解上赋值的值。
这里的redis的部分代码也贴出来
@Service
public class RedisUtils {
@Resource
private RedisTemplate redisTemplate;
/**
* 写入缓存设置时效时间
*
* @param key
* @param value
* @param expireTime 有效时间,单位秒
* @return
*/
public boolean set(final String key, Object value, Long expireTime) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
怎么获取用户的真实IP呢???如下
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
/**
* IP Utils
* @author rs
*
*/
public class IPUtils {
private static Logger logger = LoggerFactory.getLogger(IPUtils.class);
/**
* 获取IP地址
*
* 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = null;
try {
ip = request.getHeader("x-forwarded-for");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if( ip.indexOf(",")!=-1 ){
ip = ip.split(",")[0];
}
}
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.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
} catch (Exception e) {
logger.error("IPUtils ERROR ", e);
}
return ip;
}
}
下面来正式使用一下
@VisitLimit(limit = 1, sec = 1)
@RequestMapping(value = "/close", method = RequestMethod.POST)
欢迎指正交流哦!!
网友评论