这里实现一分钟内只允许5次登录废话不多说直接上代码1.使用 Lua 脚本 将“清理过期数据 → 统计计数 → 判断是否超限 → 添加新记录”四个操作封装为一个原子操作避免高并发下 count 与 add 之间的竞争条件。-- KEYS[1] : 限流key例如 login:limit:phone:138****0000 -- ARGV[1] : 当前时间戳毫秒 -- ARGV[2] : 窗口大小毫秒 -- ARGV[3] : 限流阈值最大请求次数 -- ARGV[4] : 本次请求的唯一标识例如时间戳随机数 local window_start tonumber(ARGV[1]) - tonumber(ARGV[2]) -- 1. 删除窗口外的旧数据 redis.call(ZREMRANGEBYSCORE, KEYS[1], 0, window_start) -- 2. 统计窗口内当前请求数 local current_count redis.call(ZCARD, KEYS[1]) -- 3. 判断是否超过阈值 if current_count tonumber(ARGV[3]) then -- 4. 添加本次请求记录 redis.call(ZADD, KEYS[1], ARGV[1], ARGV[4]) -- 5. 设置 key 过期时间窗口大小 1秒缓冲 redis.call(EXPIRE, KEYS[1], math.ceil(tonumber(ARGV[2]) / 1000) 1) return 1 -- 放行 else return 0 -- 限流 end2.新建一个limit包编写服务如下也可以写在service实现类中package com.hmdp.limit; import org.springframework.core.io.ClassPathResource; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.scripting.support.ResourceScriptSource; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.util.Collections; import java.util.UUID; Service public class LoginLimitService { Resource private StringRedisTemplate stringRedisTemplate; //定义一个redis lua脚本返回long类型数据 private DefaultRedisScriptLong loginLimitScript; private static final long WINDOW_SIZE_MS 60 * 1000L; // 1分钟 窗口 private static final int LIMIT_COUNT 5; PostConstruct public void init() { loginLimitScript new DefaultRedisScript(); // 加载 resources/lua/login_limit.lua loginLimitScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(login_limit.lua))); loginLimitScript.setResultType(Long.class); } public boolean tryAcquire(String phoneNumber) { String key login:limit:phone: phoneNumber; long now System.currentTimeMillis(); //获取当前系统时间的时间戳 String member now _ UUID.randomUUID().toString(); //在zset中member如果一样 后写的会覆盖前写的 保证member的唯一 Long result stringRedisTemplate.execute( loginLimitScript, Collections.singletonList(key), String.valueOf(now), String.valueOf(WINDOW_SIZE_MS), String.valueOf(LIMIT_COUNT), member ); return result ! null result 1L; } }注意这里StringRedisTemplate需要配置序列化器3.登录接口PostMapping(/login) public Result login(RequestBody LoginFormDTO loginForm, HttpSession session, HttpServletRequest request){ String phone loginForm.getPhone(); // 实现登录功能 if ( phone null){ phone IpUtils.getIp( request); //拿到ip 作为手机号 } //添加滑动窗口进行登录限流 每一分钟 最多5次 if (!loginLimitService.tryAcquire(phone)) { return Result.fail(登录尝试过于频繁请稍后再试); } return userService.login(loginForm, session); }4.如果请求手机号是空的拿到请求ip做key在config包下添加iputils如下package com.hmdp.utils; import javax.servlet.http.HttpServletRequest; //获取ip的工具类 public class IpUtils { public static String getIp(HttpServletRequest request) { String ip request.getHeader(X-Forwarded-For); if (ip null || ip.length() 0 || unknown.equalsIgnoreCase(ip)) { ip request.getHeader(Proxy-Client-IP); } if (ip null || ip.length() 0 || unknown.equalsIgnoreCase(ip)) { ip request.getHeader(WL-Proxy-Client-IP); } if (ip null || ip.length() 0 || unknown.equalsIgnoreCase(ip)) { ip request.getRemoteAddr(); } // 取第一个IP if (ip ! null ip.contains(,)) { ip ip.split(,)[0].trim(); } return ip; } }当然如果手机号码是强校验也可以省略ip来做key5.postman做测试在连续点击5次登录请求之后可以看到直接返回了登录过于频繁注意401 要带请求token6.redis中窗口如图所示