| package cc.mrbird.febs.common.aspect; | 
|   | 
| import cc.mrbird.febs.common.annotation.Limit; | 
| import cc.mrbird.febs.common.entity.LimitType; | 
| import cc.mrbird.febs.common.exception.LimitAccessException; | 
| import cc.mrbird.febs.common.utils.HttpContextUtil; | 
| import cc.mrbird.febs.common.utils.IpUtil; | 
| import com.google.common.collect.ImmutableList; | 
| import lombok.RequiredArgsConstructor; | 
| import lombok.extern.slf4j.Slf4j; | 
| import org.apache.commons.lang3.StringUtils; | 
| 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.data.redis.core.script.DefaultRedisScript; | 
| import org.springframework.data.redis.core.script.RedisScript; | 
| import org.springframework.stereotype.Component; | 
|   | 
| import javax.servlet.http.HttpServletRequest; | 
| import java.lang.reflect.Method; | 
|   | 
|   | 
| /** | 
|  * 接口限流 | 
|  * | 
|  * @author MrBird | 
|  */ | 
| @Slf4j | 
| @Aspect | 
| @Component | 
| @RequiredArgsConstructor | 
| public class LimitAspect extends BaseAspectSupport { | 
|   | 
|     private final RedisTemplate<String, Object> redisTemplate; | 
|   | 
|     @Pointcut("@annotation(cc.mrbird.febs.common.annotation.Limit)") | 
|     public void pointcut() { | 
|     } | 
|   | 
|     @Around("pointcut()") | 
|     public Object around(ProceedingJoinPoint point) throws Throwable { | 
|         HttpServletRequest request = HttpContextUtil.getHttpServletRequest(); | 
|         Method method = resolveMethod(point); | 
|         Limit limitAnnotation = method.getAnnotation(Limit.class); | 
|         LimitType limitType = limitAnnotation.limitType(); | 
|         String name = limitAnnotation.name(); | 
|         String key; | 
|         String ip = IpUtil.getIpAddr(request); | 
|         int limitPeriod = limitAnnotation.period(); | 
|         int limitCount = limitAnnotation.count(); | 
|         switch (limitType) { | 
|             case IP: | 
|                 key = ip; | 
|                 break; | 
|             case CUSTOMER: | 
|                 key = limitAnnotation.key(); | 
|                 break; | 
|             default: | 
|                 key = StringUtils.upperCase(method.getName()); | 
|         } | 
|         ImmutableList<String> keys = ImmutableList.of(StringUtils.join(limitAnnotation.prefix() + "_", key, ip)); | 
|         String luaScript = buildLuaScript(); | 
|         RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class); | 
|         Number count = redisTemplate.execute(redisScript, keys, limitCount, limitPeriod); | 
|         log.info("IP:{} 第 {} 次访问key为 {},描述为 [{}] 的接口", ip, count, keys, name); | 
|         if (count != null && count.intValue() <= limitCount) { | 
|             return point.proceed(); | 
|         } else { | 
|             throw new LimitAccessException("接口访问超出频率限制"); | 
|         } | 
|     } | 
|   | 
|     /** | 
|      * 限流脚本 | 
|      * 调用的时候不超过阈值,则直接返回并执行计算器自加。 | 
|      * | 
|      * @return lua脚本 | 
|      */ | 
|     private String buildLuaScript() { | 
|         return "local c" + | 
|                 "\nc = redis.call('get',KEYS[1])" + | 
|                 "\nif c and tonumber(c) > tonumber(ARGV[1]) then" + | 
|                 "\nreturn c;" + | 
|                 "\nend" + | 
|                 "\nc = redis.call('incr',KEYS[1])" + | 
|                 "\nif tonumber(c) == 1 then" + | 
|                 "\nredis.call('expire',KEYS[1],ARGV[2])" + | 
|                 "\nend" + | 
|                 "\nreturn c;"; | 
|     } | 
| } |