redis实现接口限流策略
This commit is contained in:
parent
0e994a168c
commit
0914ee4f37
|
|
@ -0,0 +1,35 @@
|
|||
package com.xjs.annotation;
|
||||
|
||||
import com.xjs.enums.InterfaceLimitType;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 接口限流注解
|
||||
* @author xiejs
|
||||
* @since 2022-05-16
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RateLimiter {
|
||||
/**
|
||||
* 限流key
|
||||
*/
|
||||
String key() default "rate_limit:";
|
||||
|
||||
/**
|
||||
* 限流时间,单位秒
|
||||
*/
|
||||
int time() default 60;
|
||||
|
||||
/**
|
||||
* 限流次数
|
||||
*/
|
||||
int count() default 100;
|
||||
|
||||
/**
|
||||
* 限流类型
|
||||
*/
|
||||
InterfaceLimitType limitType() default InterfaceLimitType.DEFAULT;
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.xjs.aop;
|
||||
|
||||
import com.ruoyi.common.core.utils.ip.IpUtils;
|
||||
import com.xjs.annotation.RateLimiter;
|
||||
import com.xjs.enums.InterfaceLimitType;
|
||||
import com.xjs.exception.BusinessException;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
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.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.script.RedisScript;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 接口限流aop
|
||||
* @author xiejs
|
||||
* @since 2022-05-16
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@Log4j2
|
||||
public class RateLimiterAspect {
|
||||
@Autowired
|
||||
private RedisTemplate<Object, Object> redisTemplate;
|
||||
|
||||
@Autowired
|
||||
private RedisScript<Long> limitScript;
|
||||
|
||||
@Before("@annotation(rateLimiter)")
|
||||
public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
|
||||
String key = rateLimiter.key();
|
||||
int time = rateLimiter.time();
|
||||
int count = rateLimiter.count();
|
||||
|
||||
String combineKey = getCombineKey(rateLimiter, point);
|
||||
List<Object> keys = Collections.singletonList(combineKey);
|
||||
try {
|
||||
Long number = redisTemplate.execute(limitScript, keys, count, time);
|
||||
if (number==null || number.intValue() > count) {
|
||||
throw new BusinessException("访问过于频繁,请稍候再试");
|
||||
}
|
||||
log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key);
|
||||
} catch (BusinessException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("服务器限流异常,请稍候再试");
|
||||
}
|
||||
}
|
||||
|
||||
public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
|
||||
StringBuilder stringBuffer = new StringBuilder(rateLimiter.key());
|
||||
if (rateLimiter.limitType() == InterfaceLimitType.IP) {
|
||||
stringBuffer.append(IpUtils.getIpAddr(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest())).append("-");
|
||||
}
|
||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
Class<?> targetClass = method.getDeclaringClass();
|
||||
stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
|
||||
return stringBuffer.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.xjs.enums;
|
||||
|
||||
/**
|
||||
* 接口限流类别枚举类
|
||||
* @author xiejs
|
||||
* @since 2022-05-16
|
||||
*/
|
||||
public enum InterfaceLimitType {
|
||||
|
||||
/**
|
||||
* 默认策略全局限流
|
||||
*/
|
||||
DEFAULT,
|
||||
/**
|
||||
* 根据请求者IP进行限流
|
||||
*/
|
||||
IP
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.xjs.script;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.scripting.support.ResourceScriptSource;
|
||||
|
||||
/**
|
||||
* redis 脚本执行
|
||||
* @author xiejs
|
||||
* @since 2022-05-16
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisScript {
|
||||
@Bean
|
||||
public DefaultRedisScript<Long> limitScript() {
|
||||
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
|
||||
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit.lua")));
|
||||
redisScript.setResultType(Long.class);
|
||||
return redisScript;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
local key = KEYS[1]
|
||||
local count = tonumber(ARGV[1])
|
||||
local time = tonumber(ARGV[2])
|
||||
local current = redis.call('get', key)
|
||||
if current and tonumber(current) > count then
|
||||
return tonumber(current)
|
||||
end
|
||||
current = redis.call('incr', key)
|
||||
if tonumber(current) == 1 then
|
||||
redis.call('expire', key, time)
|
||||
end
|
||||
return tonumber(current)
|
||||
|
|
@ -10,6 +10,7 @@ import com.ruoyi.common.log.annotation.Log;
|
|||
import com.ruoyi.common.log.enums.BusinessType;
|
||||
import com.ruoyi.common.security.annotation.RequiresLogin;
|
||||
import com.ruoyi.common.security.annotation.RequiresPermissions;
|
||||
import com.xjs.annotation.RateLimiter;
|
||||
import com.xjs.copywriting.domain.CopyWriting;
|
||||
import com.xjs.copywriting.domain.RequestBody;
|
||||
import com.xjs.copywriting.factory.CopyWritingFactory;
|
||||
|
|
@ -29,8 +30,9 @@ import java.util.Optional;
|
|||
|
||||
/**
|
||||
* 文案controller
|
||||
*
|
||||
* @author xiejs
|
||||
* @since 2021-12-27
|
||||
* @since 2021-12-27
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("copyWriting")
|
||||
|
|
@ -84,7 +86,7 @@ public class CopyWritingController extends BaseController {
|
|||
|
||||
//清理重复内容
|
||||
int i = copyWritingService.deleteRepeatData();
|
||||
log.info("清理文案重复数:"+i);
|
||||
log.info("清理文案重复数:" + i);
|
||||
|
||||
return R.ok(copyWriting);
|
||||
}
|
||||
|
|
@ -93,6 +95,7 @@ public class CopyWritingController extends BaseController {
|
|||
@ApiOperation("删除重复文案内容")
|
||||
@RequiresPermissions("openapi:copywriting:remove")
|
||||
@Log(title = "删除重复文案", businessType = BusinessType.DELETE)
|
||||
@RateLimiter(count = 1, time = 1)
|
||||
public AjaxResult deleteRepeatData() {
|
||||
int count = copyWritingService.deleteRepeatData();
|
||||
return AjaxResult.success(count);
|
||||
|
|
@ -152,6 +155,7 @@ public class CopyWritingController extends BaseController {
|
|||
@ApiOperation("文案列表")
|
||||
@RequiresPermissions("openapi:copywriting:list")
|
||||
@GetMapping("/list")
|
||||
@RateLimiter(count = 1, time = 1)
|
||||
public TableDataInfo list(@Validated({SelectGroup.class}) CopyWriting copyWriting) {
|
||||
startPage();
|
||||
List<CopyWriting> list = copyWritingService.selectCopyWritingList(copyWriting);
|
||||
|
|
@ -188,6 +192,7 @@ public class CopyWritingController extends BaseController {
|
|||
@Log(title = "文案管理", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
@ApiOperation("删除文案")
|
||||
@RateLimiter(count = 1, time = 1)
|
||||
public AjaxResult remove(@PathVariable Long[] ids) {
|
||||
return toAjax(copyWritingService.deleteCopyWritingByIds(ids));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.xjs.mall.product.vo.sku;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 带有 Sku Id VO 的 Attr 值
|
||||
|
|
@ -9,7 +8,6 @@ import lombok.ToString;
|
|||
* @since 2022-05-12
|
||||
*/
|
||||
@Data
|
||||
@ToString
|
||||
public class AttrValueWithSkuIdVO {
|
||||
private String attrValue;
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue