package com.fdkankan.cloud.acl.aop; import cn.hutool.core.util.StrUtil; import com.fdkankan.cloud.acl.annotation.RedisLimit; import com.fdkankan.cloud.acl.constant.LimitType; import com.fdkankan.common.constant.ErrorCode; import com.fdkankan.common.exception.BusinessException; import com.fdkankan.web.util.WebUtil; 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.concurrent.TimeUnit; @Slf4j @Aspect @Configuration public class RedisLimitAspect { private final static String REDIS_LIMIT_KEY_PREFIX = "openapi:limit:key:resource:%s:%s"; @Autowired private RedisTemplate redisTemplate; @Resource private RedisScript limitRedisScript; @Before("@annotation(com.fdkankan.cloud.acl.aop.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();//限流方式 String resource = redisLimit.name();//资源名 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, resource, limitCount, period, timeUnit); if(limited){ throw new BusinessException(ErrorCode.SYSTEM_BUSY.code(), redisLimit.msg()); } } private boolean shouldLimited(String key, String resource, long max, long timeout, TimeUnit timeUnit) { key = String.format(REDIS_LIMIT_KEY_PREFIX, key, resource); // 统一使用单位毫秒 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 script, List 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 limitRedisScript() { DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redisScript/limit.lua"))); redisScript.setResultType(Long.class); return redisScript; } }