沉默是金,总会发光
大家好,我是沉默
面试官:接口被恶意狂刷,怎么办?
我:这个……没搞过(每天 CRUD,真没搞过)
面试官:那你现在设计一个?
我:巴拉巴拉……(自己都不信的胡扯)
面试官:(明显不耐烦)那我们换个话题吧。
说实话,那一刻我就知道——
这场面试,已经凉了一半。
不是我不会写代码,
而是我从没系统想过「接口防刷」这种问题。
但后来我才发现一件事
接口防刷,其实一点都不复杂,甚至可以「一个注解搞定」。
今天这篇,我不跟你扯高深架构,
就用一个能在面试里讲清楚、在项目里用得上的防刷方案,
让你下次被问到,至少能稳稳说出 123。
**-**01-
接口防刷,本质在防什么?
很多人一听“防刷”,脑子里就冒出:
- 风控
- 限流算法
- 网关
- 黑名单
- 滑块验证
其实对 80% 的业务系统来说,根本用不上这么重。
我们今天解决的是最常见的一类问题:
同一个用户 / 同一个接口,在短时间内被疯狂请求
所以防刷的核心只有一句话:
限制「同一个人」在「同一个接口」上的访问次数
- 02-
一个注解搞定防刷
先别急着看代码,先把思路在脑子里跑通
整体设计思路(面试版)
- 用自定义注解标记哪些接口需要防刷
- 用拦截器统一拦截请求
- 用 Redis记录访问次数
- 超过阈值,直接拒绝
一句话就是:
注解定义规则,拦截器执行规则,Redis 负责记账
第一步:自定义一个防刷注解
我们先定义一个注解,用来描述「防刷规则」。
@Retention(RUNTIME)
@Target(METHOD)
public @interface AccessLimit {
// 允许访问的最大次数
intmaxCount();
// 是否需要登录
boolean needLogin()default false;
}
这个注解只干一件事:
把“防刷规则”写在方法上,而不是写死在代码里
用起来会非常优雅👇
@AccessLimit(maxCount = 5, needLogin = true)
@RequestMapping("/fangshua")
@ResponseBody
public Object fangshua() {
return"请求成功";
}
第二步:Redis 负责「记账」
防刷一定绕不开 Redis。
我们需要它做的事情很简单:
- key:请求标识
- value:访问次数
- TTL:有效期
Redis Key 设计(重点,面试加分)
URI + userId + yyyyMMdd
也就是说:
同一个用户,在同一天,对同一个接口的访问次数
第三步:拦截器里干“脏活累活”
所有真正的防刷逻辑,都在拦截器里。
核心逻辑拆解(一定要能讲)
- 判断当前请求是否是 Controller 方法
- 判断方法上有没有
@AccessLimit - 组装 Redis key
- 从 Redis 取访问次数
- 超限 → 直接拦截
核心代码(精简但完整)
@Componentpublic class FangshuaInterceptor extends HandlerInterceptorAdapter { @Resource private RedisTemplate<String, Object> redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod hm = (HandlerMethod) handler; AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class); if (accessLimit == null) { return true; } int maxCount = accessLimit.maxCount(); boolean needLogin = accessLimit.needLogin(); String key = request.getRequestURI(); if (needLogin) { Long userId = 101L; // 示例 String day = new SimpleDateFormat("yyyyMMdd").format(new Date()); key = key + ":" + userId + ":" + day; } Integer count = (Integer) redisTemplate.opsForValue().get(key); if (count == null) { redisTemplate.opsForValue().set(key, 1, 1, TimeUnit.DAYS); } else if (count < maxCount) { redisTemplate.opsForValue().increment(key); } else { response.getWriter().write("访问次数已达上限!"); return false; } return true; }}
注册拦截器(别忘了这一步)
@Configuration
public class WebConfigextends WebMvcConfigurerAdapter {
@Autowired
private FangshuaInterceptor interceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor);
}
}
到这一步,防刷链路已经完整闭环了。
效果验证
访问接口:
http://localhost:8080/fangshua
- 前 5 次:
请求成功 - 第 6 次:
访问次数已达上限
一个注解,防刷生效。
- 03-
如果面试官继续追问,你可以这么答
Q1:这种方案适合什么场景?
适合中小系统、管理后台、业务接口级防刷
不适合超高并发网关级限流
Q2:有什么优化空间?
- key 增加 IP 维度
- 使用
Redis Lua保证原子性 - 与网关限流形成双层防护
Q3:为什么不用 AOP?
拦截器更贴近请求链路,
AOP 更适合业务横切,不适合请求级控制
**-**04-
总结
接口防刷不是“高深架构”,而是“基础设计能力”
你不需要一上来就说:
- Sentinel
- Gateway
- 滑块验证码
**
**
你只要能把:
- 注解
- 拦截器
- Redis
- Key 设计
这条链路讲清楚,
面试官就已经在心里给你加分了。
**-**05-
粉丝福利
点点关注,送你互联网大厂面试题库,如果你正在找工作,又或者刚准备换工作。可以仔细阅读一下,或许对你有所帮助!