自定義註解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface RequestLimit {
/**
* 允許訪問的次數,默認值20
*/
int count() default 20;
/**
* 時間段,單位爲毫秒,默認值一分鐘
*/
long time() default 60000;
}
Aop增強類:
@Component
@Aspect
@Slf4j
public class RequestLimitAop {
private Logger LOGGER = LoggerFactory.getLogger(getClass());
@Autowired
private RedisService redisService;
@Before("within(@org.springframework.stereotype.Controller *) && @annotation(limit)")
public void requestLimit(JoinPoint joinPoint, RequestLimit limit) throws Exception {
Object[] args = joinPoint.getArgs();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
String ip = request.getRemoteAddr();
LOGGER.info("訪問的ip地址爲:{}", ip);
String url = request.getRequestURL().toString();
String key = "req_limit_".concat(url).concat("_").concat(ip);
boolean checkResult = checkByRedis(limit, key);
if (!checkResult) {
LOGGER.info("requestLimited," + "[用戶ip:{}],[訪問地址:{}]超過了限定的次數[{}]次", ip, url, limit.count());
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Type", "text/json;charset=UTF-8");
response.setHeader("icop-content-type", "exception");
PrintWriter writer = null;
JsonGenerator jsonGenerator = null;
try {
writer = response.getWriter();
jsonGenerator = (new ObjectMapper()).getFactory().createGenerator(writer);
jsonGenerator.writeObject("請求過於頻繁,超出限制!");
} catch (IOException e1) {
e1.printStackTrace();
} finally {
writer.flush();
writer.close();
}
throw new Exception("請求過於頻繁,超出限制!");
}
}
private boolean checkByRedis(RequestLimit limit, String key) {
Integer incrByCount = redisService.incrBy(key, limit);
if (incrByCount > limit.count()) {
/**
* 該次請求已經超過了規定時間範圍內請求的最大次數
*/
LOGGER.info("當前請求次數爲:{},該次請求已經超過了規定時間範圍內請求的最大次數", incrByCount);
return false;
} else {
/**
* 該次請求已經未超過了規定時間範圍內請求的最大次數,可以繼續請求
*/
LOGGER.info("當前請求次數爲:{},該次請求已經未超過了規定時間範圍內請求的最大次數,可以繼續請求", incrByCount);
return true;
}
}
使用註解:
/**
* 測試使用redis+aop實現限流,防止同一個IP在短時間內多次惡意訪問系統接口
*
* @return
*/
@RequestLimit(count = 10, time = 60000)
@RequestMapping("/secRequestLimit")
@ResponseBody
public String secRequestLimit(@RequestParam(value = "username") String username, @RequestParam(value = "stockName") String stockName) {
return username + "訪問成功";
}
使用jemeter測試:
打印的log日誌: