import java.lang.annotation.*;
/**
* @author 向振华
* @date 2022/11/21 18:16
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Limiter {
/**
* 限制时间(秒)
*
* @return
*/
long limitTime() default 2L;
/**
* 限制后的错误提示信息
*
* @return
*/
String errorMessage() default "请求频繁,请稍后重试";
}
import com.alibaba.fastjson.JSONObject;
import com.xzh.web.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.StringJoiner;
import java.util.concurrent.TimeUnit;
/**
* @author 向振华
* @date 2022/11/21 18:16
*/
@Aspect
@Component
@Slf4j
public class LimiterAspect {
@Resource
private RedisTemplate redisTemplate;
@Around("@annotation(com.xzh.aop.Limiter)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
Limiter annotation = method.getAnnotation(Limiter.class);
if (annotation != null) {
// 获取限制key
String limitKey = getKey(joinPoint);
if (limitKey != null) {
log.info("limitKey ---> " + limitKey);
Boolean hasKey = redisTemplate.hasKey(limitKey);
if (Boolean.TRUE.equals(hasKey)) {
// 返回限制后的返回内容
return ApiResponse.fail(annotation.errorMessage());
} else {
// 存入限制的key
redisTemplate.opsForValue().set(limitKey, "", annotation.limitTime(), TimeUnit.SECONDS);
}
}
}
return joinPoint.proceed();
}
public String getKey(ProceedingJoinPoint joinPoint) {
// 参数
StringJoiner asj = new StringJoiner(",");
Object[] args = joinPoint.getArgs();
Arrays.stream(args).forEach(a -> asj.add(JSONObject.toJSONString(a)));
if (asj.toString().isEmpty()) {
return null;
}
// 切入点
String joinPointString = joinPoint.getSignature().toString();
// 限制key = 切入点 + 参数
return joinPointString + ":" + asj.toString();
}
}
import com.xzh.web.ApiResponse;
import com.xzh.aop.Limiter;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 向振华
* @date 2021/11/21 18:03
*/
@RestController
public class TestController {
@Limiter(limitTime = 10L)
@PostMapping("/test1")
public ApiResponse
import org.aspectj.lang.ProceedingJoinPoint;
/**
* @author 向振华
* @date 2022/11/21 18:22
*/
public interface LimiterKeyGetter {
/**
* 获取限制key
*
* @param joinPoint
* @return
*/
String getKey(ProceedingJoinPoint joinPoint);
}
限制key = 切入点 + 请求参数,需要注意请求参数的大小,避免redis key过大。
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import java.util.Arrays;
import java.util.StringJoiner;
/**
* @author 向振华
* @date 2022/11/22 13:39
*/
public class DefaultLimiterKeyGetter implements LimiterKeyGetter {
@Override
public String getKey(ProceedingJoinPoint joinPoint) {
// 参数
StringJoiner asj = new StringJoiner(",");
Object[] args = joinPoint.getArgs();
Arrays.stream(args).forEach(a -> asj.add(JSONObject.toJSONString(a)));
if (asj.toString().isEmpty()) {
return null;
}
// 切入点
String joinPointString = joinPoint.getSignature().toString();
// 限制key = 切入点 + 参数
return joinPointString + ":" + asj.toString();
}
}
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* @author 向振华
* @date 2022/11/22 13:39
*/
public class UrlSessionLimiterKeyGetter implements LimiterKeyGetter {
@Override
public String getKey(ProceedingJoinPoint joinPoint) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
if (servletRequestAttributes == null) {
return null;
}
HttpServletRequest request = servletRequestAttributes.getRequest();
// 限制key = url + sessionId
return request.getRequestURL() + ":" + request.getSession().getId();
}
}
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import java.util.Arrays;
import java.util.StringJoiner;
/**
* @author 向振华
* @date 2022/11/22 15:38
*/
public class Sha1LimiterKeyGetter implements LimiterKeyGetter {
@Override
public String getKey(ProceedingJoinPoint joinPoint) {
// 参数
StringJoiner asj = new StringJoiner(",");
Object[] args = joinPoint.getArgs();
Arrays.stream(args).forEach(a -> asj.add(JSONObject.toJSONString(a)));
if (asj.toString().isEmpty()) {
return null;
}
// 序列号
byte[] serialize = ObjectUtil.serialize(asj.toString().hashCode());
// sha1处理
String sha1 = DigestUtil.sha1Hex(serialize).toLowerCase();
// 切入点
String joinPointString = joinPoint.getSignature().toString();
// 限制key = 切入点 + sha1值
return joinPointString + ":" + sha1;
}
}
// 获取限制key
String limitKey = null;
try {
limitKey = annotation.keyUsing().newInstance().getKey(joinPoint);
} catch (Exception ignored) {
}
/**
* @author 向振华
* @date 2022/11/22 15:50
*/
public enum ReturnStrategy {
/**
* 返回错误提示信息
*/
ERROR_MESSAGE,
/**
* 返回上次执行的结果
*/
LAST_RESULT,
}
LAST_RESULT策略的实现逻辑:
将执行结果和限制key一起存入redis,然后判断需要限制时,从redis取出执行结果并返回出去。
在被限制时,重试n次,n次后如果依然被限制,则不再重试。
等待n秒后重试1次,如果依然被限制,则不再重试。
等待n秒后重试n次,如果依然被限制,则不再重试。
import com.xzh.aop.key.DefaultLimiterKeyGetter;
import com.xzh.aop.key.LimiterKeyGetter;
import java.lang.annotation.*;
/**
* @author 向振华
* @date 2022/11/21 18:16
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Limiter {
/**
* 限制时间(秒)
*
* @return
*/
long limitTime() default 2L;
/**
* 限制后的错误提示信息
*
* @return
*/
String errorMessage() default "请求频繁,请稍后重试";
/**
* 限制key获取类
*
* @return
*/
Class extends LimiterKeyGetter> keyUsing() default DefaultLimiterKeyGetter.class;
/**
* 限制后的返回策略
*
* @return
*/
ReturnStrategy returnStrategy() default ReturnStrategy.ERROR_MESSAGE;
}