From 49ba3929b24f08abeef7d839f65ef25fcf575134 Mon Sep 17 00:00:00 2001 From: zk Date: Tue, 10 Mar 2026 20:17:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=BB=91=E5=90=8D=E5=8D=95?= =?UTF-8?q?=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jiayunet/constant/PreRedisKeyName.java | 20 ++----- .../interceptor/PreventReplayInterceptor.java | 60 ++++++++++++------- 2 files changed, 44 insertions(+), 36 deletions(-) diff --git a/common/src/main/java/org/jiayunet/constant/PreRedisKeyName.java b/common/src/main/java/org/jiayunet/constant/PreRedisKeyName.java index dc537b9..15a0a30 100644 --- a/common/src/main/java/org/jiayunet/constant/PreRedisKeyName.java +++ b/common/src/main/java/org/jiayunet/constant/PreRedisKeyName.java @@ -10,26 +10,16 @@ public interface PreRedisKeyName { /** * 防刷key名 */ - String PREVENT_REPLAY = "preventReplay:ip:"; - /** - * 黑名单ip - */ - String BLACK_LIST = "blackList:ip:"; - /** - * 单次执行任务Id - */ - String EXECUTE_SINGLE_TASK = "execute:single-task-id:"; + String PREVENT_REPLAY = "preventReplay:hash:"; + /** + * 黑名单指纹哈希值 + */ + String BLACK_LIST = "blackList:hash:"; /** * token Key名 前缀 */ String LOGIN_TOKEN = "login:token:"; - /** - * 图片验证码Key名 前缀 - */ - String LOGIN_IMAGES_CODE_UUID = "login:images:uuid:"; - - } diff --git a/common/src/main/java/org/jiayunet/interceptor/PreventReplayInterceptor.java b/common/src/main/java/org/jiayunet/interceptor/PreventReplayInterceptor.java index eeacc1c..0ce6ac8 100644 --- a/common/src/main/java/org/jiayunet/interceptor/PreventReplayInterceptor.java +++ b/common/src/main/java/org/jiayunet/interceptor/PreventReplayInterceptor.java @@ -19,7 +19,7 @@ import org.jiayunet.tool.server.RedisServerTool; /** * 防止高频重复请求 - * + * * @author zk */ @Component @@ -43,36 +43,54 @@ public class PreventReplayInterceptor implements HandlerInterceptor { @Autowired private RedisServerTool redisServerTool; + + /** + * 生成指纹:IP + User-Agent + Accept-Language + */ + private String generateFingerprint(HttpServletRequest request) { + String ip = HttpIpTool.gteRealIP(request); + String ua = request.getHeader("User-Agent"); + String lang = request.getHeader("Accept-Language"); + if (ua == null) ua = ""; + if (lang == null) lang = ""; + String raw = ip + "|" + ua + "|" + lang; + return Integer.toHexString(raw.hashCode()); + } + + @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - // 关闭时 不执行 - if (!ifOpen) { + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + // 若关闭直接放行 + if (!Boolean.TRUE.equals(ifOpen)) { return true; } - String redisKeyName = PreRedisKeyName.PREVENT_REPLAY + HttpIpTool.gteRealIP(request); + // 使用指纹作为 Redis 键 + String redisKeyName = PreRedisKeyName.PREVENT_REPLAY + generateFingerprint(request); - // 获取redis中的数据 + // 读取计数信息 RedisPreventReplayInfo replayInfo = redisServerTool.get(redisKeyName, RedisPreventReplayInfo.class); - replayInfo = Objects.nonNull(replayInfo) ? replayInfo : new RedisPreventReplayInfo(); - // 计算时间差 并更新数据 + // 检查时间窗口是否已过 Duration duration = Duration.between(replayInfo.getLastTiming(), Instant.now()); - long seconds = duration.getSeconds(); - replayInfo = seconds <= intervalTime ? replayInfo : new RedisPreventReplayInfo(); - - // 增加次数 写回redis - replayInfo.setFrequency(replayInfo.getFrequency() + 1); - redisServerTool.set(redisKeyName, replayInfo); - - boolean type = replayInfo.getFrequency() <= limitNumber; - - // 加黑 - if (!type) { - redisServerTool.set(PreRedisKeyName.BLACK_LIST + request.getRemoteAddr(), "高频请求", 1, TimeUnit.DAYS); + if (duration.getSeconds() > intervalTime) { + replayInfo = new RedisPreventReplayInfo(); } - return type; + // 更新最近一次请求时间并计数 + replayInfo.setLastTiming(Instant.now()); + replayInfo.setFrequency(replayInfo.getFrequency() + 1); + + // 写回 Redis,设置 TTL 与窗口保持一致,防止孤儿键占用空间 + redisServerTool.set(redisKeyName, replayInfo, intervalTime, TimeUnit.SECONDS); + + boolean allowed = replayInfo.getFrequency() <= limitNumber; + if (!allowed) { + // 加入黑名单,使用相同指纹键避免误拦 + redisServerTool.set(PreRedisKeyName.BLACK_LIST + generateFingerprint(request), "高频请求", 1, TimeUnit.DAYS); + response.setStatus(HttpServletResponse.SC_CONFLICT); + } + return allowed; } }