1. 配置过滤器
过滤器通过 Spring 的 FilterRegistrationBean 注册,拦截所有请求,并按顺序进行处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Configuration public class UserConfiguration {
@Bean @ConditionalOnProperty(name = "short-link.flow-limit.enable", havingValue = "true") public FilterRegistrationBean<UserFlowRiskControlFilter> globalUserFlowRiskControlFilter( StringRedisTemplate stringRedisTemplate, UserFlowRiskControlConfiguration userFlowRiskControlConfiguration) { FilterRegistrationBean<UserFlowRiskControlFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(new UserFlowRiskControlFilter(stringRedisTemplate, userFlowRiskControlConfiguration)); registration.addUrlPatterns("/*"); registration.setOrder(10); return registration; } }
|
2. 用户流量风控过滤器
Redis + Lua 脚本用于计算和判断用户在一定时间窗口内的请求次数。
流量超限处理:如果用户请求次数超过了配置的最大次数,将会返回错误信息,拒绝继续处理该请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
@Slf4j @RequiredArgsConstructor public class UserFlowRiskControlFilter implements Filter {
private final StringRedisTemplate stringRedisTemplate; private final UserFlowRiskControlConfiguration userFlowRiskControlConfiguration;
private static final String USER_FLOW_RISK_CONTROL_LUA_SCRIPT_PATH = "lua/user_flow_risk_control.lua";
@SneakyThrows @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(USER_FLOW_RISK_CONTROL_LUA_SCRIPT_PATH))); redisScript.setResultType(Long.class); String username = Optional.ofNullable(UserContext.getUsername()).orElse("other"); Long result; try { result = stringRedisTemplate.execute(redisScript, Lists.newArrayList(username), userFlowRiskControlConfiguration.getTimeWindow()); } catch (Throwable ex) { log.error("执行用户请求流量限制LUA脚本出错", ex); returnJson((HttpServletResponse) response, JSON.toJSONString(Results.failure(new ClientException(FLOW_LIMIT_ERROR)))); return; } if (result == null || result > userFlowRiskControlConfiguration.getMaxAccessCount()) { returnJson((HttpServletResponse) response, JSON.toJSONString(Results.failure(new ClientException(FLOW_LIMIT_ERROR)))); return; } filterChain.doFilter(request, response); }
private void returnJson(HttpServletResponse response, String json) throws Exception { response.setCharacterEncoding("UTF-8"); response.setContentType("text/html; charset=utf-8"); try (PrintWriter writer = response.getWriter()) { writer.print(json); } } }
|
3. 用户请求流量限制 Lua 脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| local username = KEYS[1] local timeWindow = tonumber(ARGV[1])
local accessKey = "short-link:user-flow-risk-control:" .. username
local currentAccessCount = redis.call("INCR", accessKey)
if currentAccessCount == 1 then redis.call("EXPIRE", accessKey, timeWindow) end
return currentAccessCount
|