1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
2.接口防刷注解类
import java.lang.annotation.*;
/**
* 接口防刷注解类
*
* @author Manaphy
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Limiter {
/**
* 从第一次访问接口的时间到cycle周期时间内,无法超过frequency次
*/
int frequency() default 20;
/**
* 周期时间,单位ms:
* 默认周期时间为一分钟
*/
long cycle() default 60 * 1000;
/**
* 返回的错误信息
*/
String message() default "请求过于频繁";
/**
* 到期时间,单位s:
* 如果在cycle周期时间内超过frequency次,则默认1分钟内无法继续访问
*/
long expireTime() default 1 * 60;
}
3. ip防刷api接口实现
import com.cgp.cache.annotation.Limiter;
import com.cgp.cache.exception.FrequentRequestsException;
import com.cgp.cache.utils.WebUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* ip防刷api功能实现。
* 该功能使用redis作为存储,方便在集群中使用。
* 如果是单项目部署,可以将redis换成本地缓存。
*
* @author Manaphy
*/
@Aspect
@Component
public class LimitingAspect {
private static final String LIMITING_KEY = "limiting:%s:%s";
private static final String LIMITING_BEGIN_TIME = "beginTime";
private static final String LIMITING_EX_FREQUENCY = "exFrequency";
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Pointcut("@annotation(limiter)")
public void pointcut(Limiter limiter) {
}
@Around(value = "pointcut(limiter)", argNames = "pjp,limiter")
public Object around(ProceedingJoinPoint pjp, Limiter limiter) throws Throwable {
//获取请求的ip和方法
String ipAddress = WebUtil.getIpAddress();
String methodName = pjp.getSignature().toLongString();
//获取方法的访问周期和频率
long cycle = limiter.cycle();
int frequency = limiter.frequency();
long currentTime = System.currentTimeMillis();
//获取redis中周期内第一次访问方法的时间和执行的次数
Long beginTimeLong = (Long) redisTemplate.opsForHash().get(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_BEGIN_TIME);
Integer exFrequencyLong = (Integer) redisTemplate.opsForHash().get(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_EX_FREQUENCY);
long beginTime = beginTimeLong == null ? 0L : beginTimeLong;
int exFrequency = exFrequencyLong == null ? 0 : exFrequencyLong;
//如果当前时间减去周期内第一次访问方法的时间大于周期时间,则正常访问
//并将周期内第一次访问方法的时间和执行次数初始化
if (currentTime - beginTime > cycle) {
redisTemplate.opsForHash().put(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_BEGIN_TIME, currentTime);
redisTemplate.opsForHash().put(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_EX_FREQUENCY, 1);
redisTemplate.expire(String.format(LIMITING_KEY, ipAddress, methodName), limiter.expireTime(), TimeUnit.SECONDS);
return pjp.proceed();
} else {
//如果在周期时间内,执行次数小于频率,则正常访问
//并将执行次数加一
if (exFrequency < frequency) {
redisTemplate.opsForHash().put(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_EX_FREQUENCY, exFrequency + 1);
redisTemplate.expire(String.format(LIMITING_KEY, ipAddress, methodName), limiter.expireTime(), TimeUnit.SECONDS);
return pjp.proceed();
} else {
//否则抛出访问频繁异常
throw new FrequentRequestsException(limiter.message());
}
}
}
}
WebUtil工具类
package com.cgp.cache.utils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author Manaphy
*/
public class WebUtil {
private static final String UNKNOWN = "unknown";
/**
* 获取request
*
* @return HttpServletRequest
*/
public static HttpServletRequest getRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
/**
* 获取response
*
* @return HttpServletResponse
*/
public static HttpServletResponse getResponse() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
}
/**
* 获取ip地址
*
* @return ip
*/
public static String getIpAddress() {
HttpServletRequest request = getRequest();
String ip = request.getHeader("x-forwarded-for");
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.getRemoteAddr();
}
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("X-Real-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
String regex = ",";
if (ip != null && ip.indexOf(regex) > 0) {
ip = ip.split(regex)[0];
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}
}
4.在需要的接口上加上@Limiter
注解
@GetMapping("/emp/{id}")
@Limiter(frequency = 3)
public Employee getEmp(@PathVariable("id") Integer id) {
return employeeService.getEmp(id);
}