forked from elunez/eladmin
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
6 changed files
with
192 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package me.zhengjie.common.aop.limit; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
/** | ||
* @author jacky | ||
*/ | ||
@Target(ElementType.METHOD) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
public @interface Limit { | ||
|
||
// 资源名称,用于描述接口功能 | ||
String name() default ""; | ||
|
||
// 资源 key | ||
String key() default ""; | ||
|
||
// key prefix | ||
String prefix() default ""; | ||
|
||
// 时间的,单位秒 | ||
int period(); | ||
|
||
// 限制访问次数 | ||
int count(); | ||
|
||
// 限制类型 | ||
LimitType limitType() default LimitType.CUSTOMER; | ||
|
||
} |
85 changes: 85 additions & 0 deletions
85
src/main/java/me/zhengjie/common/aop/limit/LimitAspect.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package me.zhengjie.common.aop.limit; | ||
|
||
import com.google.common.collect.ImmutableList; | ||
import me.zhengjie.common.exception.BadRequestException; | ||
import me.zhengjie.common.utils.IpUtil; | ||
import me.zhengjie.common.utils.RequestHolder; | ||
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.aspectj.lang.reflect.MethodSignature; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
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; | ||
|
||
@Aspect | ||
@Component | ||
public class LimitAspect { | ||
@Autowired | ||
private RedisTemplate redisTemplate; | ||
private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class); | ||
|
||
|
||
@Pointcut("@annotation(Limit)") | ||
public void pointcut() { | ||
// | ||
} | ||
|
||
@Around("pointcut()") | ||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable { | ||
HttpServletRequest request = RequestHolder.getHttpServletRequest(); | ||
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); | ||
Method signatureMethod = signature.getMethod(); | ||
Limit limit = signatureMethod.getAnnotation(Limit.class); | ||
LimitType limitType = limit.limitType(); | ||
String name = limit.name(); | ||
String key = limit.key(); | ||
if (StringUtils.isEmpty(key)) { | ||
switch (limitType) { | ||
case IP: | ||
key = IpUtil.getIP(request); | ||
break; | ||
default: | ||
key = signatureMethod.getName(); | ||
} | ||
} | ||
|
||
ImmutableList keys = ImmutableList.of(StringUtils.join(limit.prefix(), "_", key, "_", request.getRequestURI().replaceAll("/","_"))); | ||
|
||
String luaScript = buildLuaScript(); | ||
RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class); | ||
Number count = (Number) redisTemplate.execute(redisScript, keys, limit.count(), limit.period()); | ||
if (null != count && count.intValue() <= limit.count()) { | ||
logger.info("第{}次访问key为 {},描述为 [{}] 的接口", count, keys, limit.name()); | ||
return joinPoint.proceed(); | ||
} else { | ||
throw new BadRequestException("访问次数受限制"); | ||
} | ||
} | ||
|
||
/** | ||
* 限流脚本 | ||
*/ | ||
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;"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package me.zhengjie.common.aop.limit; | ||
|
||
public enum LimitType { | ||
CUSTOMER, | ||
// by ip addr | ||
IP; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package me.zhengjie.tools.rest; | ||
|
||
import me.zhengjie.common.aop.limit.Limit; | ||
import org.springframework.security.access.prepost.PreAuthorize; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import java.util.concurrent.atomic.AtomicInteger; | ||
|
||
@RestController | ||
@RequestMapping("test") | ||
public class TestController { | ||
private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(); | ||
|
||
/** | ||
* 测试限流注解,下面配置说明该接口 60秒内最多只能访问 10次,保存到redis的键名为 limit_test, | ||
*/ | ||
@Limit(key = "test", period = 60, count = 10, name = "testLimit", prefix = "limit") | ||
@GetMapping("limit") | ||
public int testLimit() { | ||
return ATOMIC_INTEGER.incrementAndGet(); | ||
} | ||
} |