自定义注解,具体频次,根据具体场景设置
import java.lang.annotation.*;
({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
(RetentionPolicy.RUNTIME)
/**
* 限流注解
*/
public @interface AccessLimit {
//标识 指定sec时间段内的访问次数限制
int limit() default 5;
//标识 时间段
int sec() default 5;
}
拦截器
import com.zubus.commonUtils.entity.BusyBusinessException;
import com.zubus.commonUtils.tool.IpUtils;
import com.zubus.mobileApi.annotation.AccessLimit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* @description: 限流拦截器
* @author: x x
* @create: 2021-11-18 09:45
*/
public class AccessLimitInterceptor implements HandlerInterceptor {
//使用RedisTemplate操作redis
private RedisTemplate redisTemplate;
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(AccessLimit.class)) {
return true;
}
AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);
if (accessLimit == null) {
return true;
}
int limit = accessLimit.limit();
int sec = accessLimit.sec();
String key = IpUtils.getIpAddr(request) + request.getRequestURI();
Integer maxLimit = (Integer) redisTemplate.opsForValue().get(key);
if (maxLimit == null) {
//set时一定要加过期时间
redisTemplate.opsForValue().set(key, 1, sec, TimeUnit.SECONDS);
} else if (maxLimit < limit) {
redisTemplate.opsForValue().set(key, maxLimit + 1, sec, TimeUnit.SECONDS);
} else {
throw new BusyBusinessException("请求太频繁");
//output(response, "请求太频繁!");
//return false;
}
}
return true;
}
public void output(HttpServletResponse response, String msg) throws IOException {
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
outputStream.write(msg.getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
} finally {
outputStream.flush();
outputStream.close();
}
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
注册/配置拦截器
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import javax.annotation.Resource;
/**
* @description: 拦截器注册
* @author: x x
* @create: 2021-11-18 09:57
*/
public class WebConfig extends WebMvcConfigurerAdapter {
private AccessLimitInterceptor accessLimitInterceptor;
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(accessLimitInterceptor);
}
}
自定义异常
public class BusyBusinessException extends RuntimeException{
public BusyBusinessException() {}
public BusyBusinessException(String exceptionInfo) {
super(exceptionInfo);
}
}
全局异常处理
public class GlobalException {
public class GlobalControllerExceptionHandler {
/*
* 统一异常处理
*/
(value = Exception.class)
// 返回json数据
public ApiResult defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
ApiResult api = new ApiResult();
String info = "系统异常,请联系管理员";
e.printStackTrace();
if(e instanceof UnauthorizedException) {
info = "没有权限";
//throw e;
}else if(e instanceof HttpRequestMethodNotSupportedException) {
throw e;
//api.setCode(405);
//return api.failed("请先登录");
}else if(e instanceof MaxUploadSizeExceededException) {
info = "文件太大";
}else if (e instanceof BusyBusinessException) {
return api.failed(e.getMessage());
}
return api.failed(info);
}
}
}
测试接口
"/noAuth/hello")
()
public ApiResult hello(HttpSession session) {
return new ApiResult().success("helle");
}
(
测试,连续访问6次
本地测试达到预期效果,线上测试,有待观察,仅供参考。