mirror of
https://github.com/201206030/novel.git
synced 2025-04-27 07:30:50 +00:00
feat: 集成 Sentinel 实现接口防刷和限流
This commit is contained in:
parent
9894814fe4
commit
c628104a30
15
pom.xml
15
pom.xml
@ -111,18 +111,31 @@
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MQ 相关-->
|
||||
<!-- MQ 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- XXL-JOB 相关 -->
|
||||
<dependency>
|
||||
<groupId>com.xuxueli</groupId>
|
||||
<artifactId>xxl-job-core</artifactId>
|
||||
<version>${xxl-job.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sentinel 相关 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-core</artifactId>
|
||||
<version>1.8.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-parameter-flow-control</artifactId>
|
||||
<version>1.8.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
|
@ -82,6 +82,16 @@ public enum ErrorCodeEnum {
|
||||
* */
|
||||
USER_UN_AUTH("A0301","访问未授权"),
|
||||
|
||||
/**
|
||||
* 用户请求服务异常
|
||||
* */
|
||||
USER_REQ_EXCEPTION("A0500","用户请求服务异常"),
|
||||
|
||||
/**
|
||||
* 请求超出限制
|
||||
* */
|
||||
USER_REQ_MANY("A0501","请求超出限制"),
|
||||
|
||||
/**
|
||||
* 用户评论异常
|
||||
* */
|
||||
@ -137,11 +147,11 @@ public enum ErrorCodeEnum {
|
||||
/**
|
||||
* 错误码
|
||||
* */
|
||||
private String code;
|
||||
private final String code;
|
||||
|
||||
/**
|
||||
* 中文描述
|
||||
* */
|
||||
private String message;
|
||||
private final String message;
|
||||
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import io.github.xxyopen.novel.core.constant.ApiRouterConsts;
|
||||
import io.github.xxyopen.novel.core.constant.SystemConfigConsts;
|
||||
import io.github.xxyopen.novel.core.interceptor.AuthInterceptor;
|
||||
import io.github.xxyopen.novel.core.interceptor.FileInterceptor;
|
||||
import io.github.xxyopen.novel.core.interceptor.FlowLimitInterceptor;
|
||||
import io.github.xxyopen.novel.core.interceptor.TokenParseInterceptor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@ -22,6 +23,8 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
@RequiredArgsConstructor
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
private final FlowLimitInterceptor flowLimitInterceptor;
|
||||
|
||||
private final AuthInterceptor authInterceptor;
|
||||
|
||||
private final FileInterceptor fileInterceptor;
|
||||
@ -30,9 +33,16 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
|
||||
// 流量限制拦截器
|
||||
registry.addInterceptor(flowLimitInterceptor)
|
||||
.addPathPatterns("/**")
|
||||
.order(0);
|
||||
|
||||
// 文件访问拦截
|
||||
registry.addInterceptor(fileInterceptor)
|
||||
.addPathPatterns(SystemConfigConsts.IMAGE_UPLOAD_DIRECTORY + "**");
|
||||
.addPathPatterns(SystemConfigConsts.IMAGE_UPLOAD_DIRECTORY + "**")
|
||||
.order(1);
|
||||
|
||||
// 权限认证拦截
|
||||
registry.addInterceptor(authInterceptor)
|
||||
@ -45,12 +55,14 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
// 放行登录注册相关请求接口
|
||||
.excludePathPatterns(ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/register",
|
||||
ApiRouterConsts.API_FRONT_USER_URL_PREFIX + "/login",
|
||||
ApiRouterConsts.API_ADMIN_URL_PREFIX + "/login");
|
||||
ApiRouterConsts.API_ADMIN_URL_PREFIX + "/login")
|
||||
.order(2);
|
||||
|
||||
// Token 解析拦截器
|
||||
registry.addInterceptor(tokenParseInterceptor)
|
||||
// 拦截小说内容查询接口,需要解析 token 以判断该用户是否有权阅读该章节(付费章节是否已购买)
|
||||
.addPathPatterns(ApiRouterConsts.API_FRONT_BOOK_URL_PREFIX + "/content/*");
|
||||
.addPathPatterns(ApiRouterConsts.API_FRONT_BOOK_URL_PREFIX + "/content/*")
|
||||
.order(3);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,98 @@
|
||||
package io.github.xxyopen.novel.core.interceptor;
|
||||
|
||||
import com.alibaba.csp.sentinel.Entry;
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
import com.alibaba.csp.sentinel.SphU;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.github.xxyopen.novel.core.common.constant.ErrorCodeEnum;
|
||||
import io.github.xxyopen.novel.core.common.resp.RestResp;
|
||||
import io.github.xxyopen.novel.core.common.util.IpUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 流量限制 拦截器
|
||||
* 实现接口防刷和限流
|
||||
*
|
||||
* @author xiongxiaoyang
|
||||
* @date 2022/6/1
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class FlowLimitInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 定义一个对所有的请求进行统一限制的资源
|
||||
*/
|
||||
private static final String ALL_LIMIT_RESOURCE = "allLimitResource";
|
||||
|
||||
static {
|
||||
// 接口限流规则:所有的请求,限制每秒最多只能通过 2000 个,超出限制匀速排队
|
||||
List<FlowRule> rules = new ArrayList<>();
|
||||
FlowRule rule1 = new FlowRule();
|
||||
rule1.setResource(ALL_LIMIT_RESOURCE);
|
||||
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
|
||||
// Set limit QPS to 2000.
|
||||
rule1.setCount(2000);
|
||||
rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
|
||||
rules.add(rule1);
|
||||
FlowRuleManager.loadRules(rules);
|
||||
|
||||
// 接口防刷规则 1:所有的请求,限制每个 IP 每秒最多只能通过 50 个,超出限制直接拒绝
|
||||
ParamFlowRule rule2 = new ParamFlowRule(ALL_LIMIT_RESOURCE)
|
||||
.setParamIdx(0)
|
||||
.setCount(50);
|
||||
// 接口防刷规则 2:所有的请求,限制每个 IP 每分钟最多只能通过 1000 个,超出限制直接拒绝
|
||||
ParamFlowRule rule3 = new ParamFlowRule(ALL_LIMIT_RESOURCE)
|
||||
.setParamIdx(0)
|
||||
.setCount(1000)
|
||||
.setDurationInSec(60);
|
||||
ParamFlowRuleManager.loadRules(Arrays.asList(rule2, rule3));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
String ip = IpUtils.getRealIp(request);
|
||||
Entry entry = null;
|
||||
try {
|
||||
// 若需要配置例外项,则传入的参数只支持基本类型。
|
||||
// EntryType 代表流量类型,其中系统规则只对 IN 类型的埋点生效
|
||||
// count 大多数情况都填 1,代表统计为一次调用。
|
||||
entry = SphU.entry(ALL_LIMIT_RESOURCE, EntryType.IN, 1, ip);
|
||||
// Your logic here.
|
||||
return HandlerInterceptor.super.preHandle(request, response, handler);
|
||||
} catch (BlockException ex) {
|
||||
// Handle request rejection.
|
||||
log.info("IP:{}被限流了!", ip);
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.getWriter().write(objectMapper.writeValueAsString(RestResp.fail(ErrorCodeEnum.USER_REQ_MANY)));
|
||||
} finally {
|
||||
// 注意:exit 的时候也一定要带上对应的参数,否则可能会有统计错误。
|
||||
if (entry != null) {
|
||||
entry.exit(1, ip);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user