- 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
- return ApiResponse.success("成功");
- }
-
- @Limiter
- @PostMapping("/test2")
- public ApiResponse
- return ApiResponse.success("成功");
- }
- }
- 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;
- }