概要
通过aop切面+redis实现接口限流,ip限制,防重复提交
整体架构流程
提示:通过aop切面+redis实现接口限流,ip限制,防重复提交
接口 文件
package com.bzfar.config;
import com.bzfar.enums.LimitType;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimiter{
/**
* 限流key
*/
String key() default "rate_limit:";
/**
* 限流时间,单位秒
*/
int time() default 60;
/**
* 限流次数
*/
int count() default 100;
/**
* 限流类型
*/
LimitType limitType() default LimitType.DEFAULT;
}
package com.bzfar.config;
import com.aspose.words.net.System.Data.DataException;
import com.bzfar.HeadContext;
import com.bzfar.enums.LimitType;
import com.bzfar.util.RedisUtil;
import com.bzfar.utils.IpUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
@Aspect
@Component
@Slf4j
public class RateLimiterAspect {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RedisUtil redisUtil;
@Before("@annotation(rateLimiter)")
public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
String key = rateLimiter.key();
Long time = new Long(rateLimiter.time());
int count = rateLimiter.count();
String combineKey = getCombineKey(rateLimiter, point);
String keyCode = key + combineKey.hashCode();
int number = 1;
if(redisUtil.hasKey(keyCode)){
number = (Integer)redisUtil.get(keyCode);
++number;
}
redisUtil.set(keyCode , number , time);
if(number > count){
throw new DataException("访问过于频繁,请稍候再试");
}
}
@After("@annotation(rateLimiter)")
public void doAfter(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
String combineKey = getCombineKey(rateLimiter, point);
redisTemplate.delete(combineKey);
}
public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
if (rateLimiter.limitType() == LimitType.IP) {
stringBuffer.append(IpUtil.getIp()).append("-");
}
if(rateLimiter.limitType() == LimitType.USER){
stringBuffer.append(HeadContext.getToken()).append("-");
}
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Class<?> targetClass = method.getDeclaringClass();
stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
return stringBuffer.toString();
}
}
type文件
package com.bzfar.enums;
import io.swagger.annotations.ApiModel;
import lombok.Getter;
@ApiModel("限流类型")
@Getter
public enum LimitType {
/** 默认策略全局限流 */
DEFAULT,
/** ip限流 */
IP,
/** 用户id限流 */
USER
}
package com.bzfar.submit;
import java.lang.annotation.*;
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit
{
}
package com.bzfar.submit;
import com.bzfar.util.AssertUtil;
import org.springframework.messaging.handler.HandlerMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
@Component
public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter
{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
if (handler instanceof HandlerMethod)
{
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
if (annotation != null)
{
if (this.isRepeatSubmit(request))
{
AssertUtil.assertNull("", "不允许重复提交,请稍后再试");
return false;
}
}
return true;
}
else
{
return super.preHandle(request, response, handler);
}
}
/**
* 验证是否重复提交由子类实现具体的防重复提交的规则
*
* @param request
* @return
* @throws Exception
*/
public abstract boolean isRepeatSubmit(HttpServletRequest request) throws Exception;
}
ip工具类
package com.bzfar.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Objects;
@Slf4j
public class IpUtil {
// 多次反向代理后会有多个ip值 的分割符
private static final String IP_UTILS_FLAG = ",";
// 未知IP
private static final String UNKNOWN = "unknown";
// 本地 IP
private static final String LOCALHOST_IP = "0:0:0:0:0:0:0:1";
private static final String LOCALHOST_IP1 = "127.0.0.1";
public static String getIp(){
// 根据 HttpHeaders 获取 请求 IP地址
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String ip = request.getHeader("X-Forwarded-For");
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("x-forwarded-for");
if (ip != null && ip.length() != 0 && !UNKNOWN.equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if (ip.contains(IP_UTILS_FLAG)) {
ip = ip.split(IP_UTILS_FLAG)[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");
}
//兼容k8s集群获取ip
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = Objects.requireNonNull(request.getRemoteAddr());
if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) {
//根据网卡取本机配置的IP
InetAddress iNet = null;
try {
iNet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
log.error("getClientIp error: ", e);
}
assert iNet != null;
ip = iNet.getHostAddress();
}
}
return ip;
}
}
controller层注解
@PostMapping("addOrUpdateUser")
@ApiOperation("新增或修改用户信息及人脸")
@RateLimiter(time = 2,count = 1 , limitType = LimitType.IP)
public HttpResult addOrUpdateUserFace(@Validated @RequestBody AddUserDto dto){
return HttpResult.ok( userService.addUserFace(dto));
}
小结
提示:
@RateLimiter(time = 2,count = 1 , limitType = LimitType.IP) 注解开启会根据固定的ip没2秒允许访问 1次