|
@@ -0,0 +1,116 @@
|
|
|
+package com.fdkankan.openApi.component;
|
|
|
+
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
+import com.fdkankan.common.constant.ErrorCode;
|
|
|
+import com.fdkankan.common.exception.BusinessException;
|
|
|
+import com.fdkankan.openApi.constant.LimitType;
|
|
|
+import com.fdkankan.web.util.WebUtil;
|
|
|
+import com.google.common.collect.ImmutableList;
|
|
|
+import jodd.datetime.TimeUtil;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.aspectj.lang.JoinPoint;
|
|
|
+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.context.annotation.Bean;
|
|
|
+import org.springframework.context.annotation.Configuration;
|
|
|
+import org.springframework.core.io.ClassPathResource;
|
|
|
+import org.springframework.data.redis.core.RedisTemplate;
|
|
|
+import org.springframework.data.redis.core.script.DefaultRedisScript;
|
|
|
+import org.springframework.data.redis.core.script.RedisScript;
|
|
|
+import org.springframework.scripting.support.ResourceScriptSource;
|
|
|
+import org.springframework.web.context.request.RequestContextHolder;
|
|
|
+import org.springframework.web.context.request.ServletRequestAttributes;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import java.lang.reflect.Method;
|
|
|
+import java.time.Instant;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Objects;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Aspect
|
|
|
+@Configuration
|
|
|
+public class RedisLimitAspect {
|
|
|
+
|
|
|
+ private final static String REDIS_LIMIT_KEY_PREFIX = "track:limit:";
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private RedisTemplate redisTemplate;
|
|
|
+ @Resource
|
|
|
+ private RedisScript<Long> limitRedisScript;
|
|
|
+
|
|
|
+ @Before("@annotation(com.fdkankan.openApi.component.RedisLimit)")
|
|
|
+ public void before(JoinPoint joinPoint){
|
|
|
+ MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
|
|
|
+ Method method = methodSignature.getMethod();
|
|
|
+ RedisLimit redisLimit = method.getAnnotation(RedisLimit.class);
|
|
|
+ LimitType limitType = redisLimit.limitType();
|
|
|
+
|
|
|
+ ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
|
|
+ HttpServletRequest request = requestAttributes.getRequest();
|
|
|
+ int period = redisLimit.period();
|
|
|
+ int limitCount = redisLimit.limitCount();
|
|
|
+ TimeUnit timeUnit = redisLimit.timeUnit();
|
|
|
+ String key = null;
|
|
|
+ switch (limitType){
|
|
|
+ case APP_KEY:
|
|
|
+ key = request.getHeader("Authorization");
|
|
|
+ if (StrUtil.isEmpty(key)) {
|
|
|
+ throw new BusinessException(ErrorCode.AUTH_FAIL);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case IP:
|
|
|
+ key = WebUtil.getIpAddress(request);
|
|
|
+ break;
|
|
|
+ case CUSTOMER:
|
|
|
+ key = redisLimit.key();
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ key = StringUtils.upperCase(method.getName());
|
|
|
+ }
|
|
|
+ boolean limited = this.shouldLimited(key, limitCount, period, timeUnit);
|
|
|
+ if(limited){
|
|
|
+ throw new BusinessException(ErrorCode.SYSTEM_BUSY);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean shouldLimited(String key, long max, long timeout, TimeUnit timeUnit) {
|
|
|
+ // 最终的 key 格式为:
|
|
|
+ // limit:自定义key:IP
|
|
|
+ // limit:类名.方法名:IP
|
|
|
+ key = REDIS_LIMIT_KEY_PREFIX + key;
|
|
|
+ // 统一使用单位毫秒
|
|
|
+ long ttl = timeUnit.toMillis(timeout);
|
|
|
+ // 当前时间毫秒数
|
|
|
+ long now = Instant.now().toEpochMilli();
|
|
|
+ long expired = now - ttl;
|
|
|
+ /**
|
|
|
+ * 注意这里必须转为 String,否则会报错 java.lang.Long cannot be cast to java.lang.String
|
|
|
+ * stringRedisTemplate.execute(RedisScript<T> script, List<K> keys, Object... args)
|
|
|
+ */
|
|
|
+ Long executeTimes = (Long)redisTemplate.execute(limitRedisScript, Collections.singletonList(key), String.valueOf(now), String.valueOf(ttl), String.valueOf(expired), String.valueOf(max));
|
|
|
+ if (executeTimes != null) {
|
|
|
+ if (executeTimes == 0) {
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Bean("limitRedisScript")
|
|
|
+ public RedisScript<Long> limitRedisScript() {
|
|
|
+ DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
|
|
|
+ redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redisScript/limit.lua")));
|
|
|
+ redisScript.setResultType(Long.class);
|
|
|
+ return redisScript;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|