YOLOv8实战:从零构建高精度宠物识别系统
2026/10/28 2:32:07
Controller / Service 方法上标 @RateLimit(...) ↓ 请求进来 AOP 切面拦截(@Around) ↓ 构建限流 key 把 key + 阈值 + 窗口 传给 Lua 脚本 ↓ Redis 原子执行计数 ↓ 返回 1(放行)/ 0(拒绝) 切面:0 → 抛异常挡下;1 → 放行继续执行业务//案例 1:弹幕限流( DanmakuServiceImpl )——每个用户每 1 秒最多发 1 条弹幕:@RateLimit(prefix="danmaku_send",// 业务前缀maxCount=1,// 窗口内最多 1 次windowSeconds=1,// 窗口 1 秒type=RateLimit.LimitType.CUSTOM,// 自定义 keykeyExpression="#dto.participantId")// 用参与者的 ID 作为 keypublicvoidsend(DanmakuDTOdto){...}@Around("@annotation(rateLimit)")// 拦截所有标了 @RateLimit 的方法publicObjectaround(ProceedingJoinPointjoinPoint,RateLimitrateLimit){StringlimitKey=buildLimitKey(joinPoint,rateLimit);// ① 拼 keyLongresult=stringRedisTemplate.execute(rateLimitScript,Collections.singletonList(limitKey),// KEYS[1]String.valueOf(rateLimit.maxCount()),// ARGV[1] 最大次数String.valueOf(rateLimit.windowSeconds()));// ARGV[2] 窗口秒数if(result==null||result==0){// ② 被拒了thrownewBusinessException(429,rateLimit.message());// ③ 抛429异常}returnjoinPoint.proceed();// ④ 放行,继续执行原方法}-- KEYS[1] = 限流 key,比如 rate_limit:danmaku:192.168.1.1-- ARGV[1] = 窗口内最大请求次数(比如 3)-- ARGV[2] = 时间窗口秒数(比如 60)localkey=KEYS[1]-- ① 取出 keylocalmaxCount=tonumber(ARGV[1])-- ② 最大次数localwindowSeconds=tonumber(ARGV[2])-- ③ 窗口秒数localcurrent=redis.call('INCR',key)-- ④ 关键:原子+1ifcurrent==1then-- ⑤ 第一次请求?redis.call('EXPIRE',key,windowSeconds)-- ⑥ 才开始计时窗口endifcurrent>maxCountthen-- ⑦ 超了?return0-- ⑧ 拒绝endreturn1-- ⑨ 放行StringbaseKey="rate_limit:"+rateLimit.prefix();// IP → rate_limit:scan_login:ip:192.168.1.1// USER → rate_limit:xxx:user:123// CUSTOM → rate_limit:danmaku_send:custom:456用户疯狂点"发弹幕" ↓ DanmakuServiceImpl.send() 标着 @RateLimit ↓ RateLimitAspect 拦截(AOP @Around) ↓ 拼 key → rate_limit:danmaku_send:custom:456 ↓ StringRedisTemplate.execute(Lua脚本, key, 1, 1) ↓ Redis 原子执行: INCR rate_limit:danmaku_send:custom:456 → 1(第1次) 是第一次 → EXPIRE 1秒 1 > 1? 否 → 返回 1(放行)✓ ······ 1秒内又来一次: INCR → 2 → 2 > 1? 是 → 返回 0(拒绝)✗ ↓ 切面收到 0 → throw BusinessException(429, "请求过于频繁...") ↓ 全局异常处理 → 前端收到 {code:429, message:"..."}