SpringBoot接口防刷(限制指定用户对指定接口指定时间范围内的访问次数)

Spring Boot项目接口防刷教程

技术要点 : Spring Boot 的基础知识 , redis基本操作

  • 接口防刷,顾名思义就是想让某个人在某个时间段只能请求指定接口的指定次数 , 在项目中这种刷接口的人也有,那就是连续点击按钮导致请求多次

原理 :

在你请求的时候,服务器通过redis记录下你的请求次数,如果次数超出限制,则不给访问,在redis保存key是有时效性的,过期就会删除。

代码实现

这次我们使用 注解 拦截器 缓存 来实现这个功能

  1. 创建一个自定义注解
package com.example.annotation;

import java.lang.annotation.*;

/**
 * @author lep
 * @date 2022-08-05 15:41
 */
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {

    /**
     * 在  seconds 秒内 , 最大只能请求 maxCount 次
     *
     * @return
     */
    //    秒
    int seconds() default 1;

    //    最大数量
    int maxCount() default 1;
}
  1. 创建一个拦截器 (用于拦截请求,更新当前用户访问的次数,如果访问受限,则返回超时的状态码)
package com.example.interceptor;

import com.example.annotation.AccessLimit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;

/**
 * 拦截器
 *
 * @author lep
 * @date 2022-08-05 15:45
 */
@Component
public class FangshuaInterceptor extends HandlerInterceptorAdapter {

    @Resource
    RedisTemplate<String, Object> redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

//       1. 判断请求是否属于方法的请求
        if (handler instanceof HandlerMethod) {
//       2. 取出当前方法的对象
            HandlerMethod handler1 = (HandlerMethod) handler;
//       3. 获取方法中的注解,看是否有该注解
            AccessLimit accessLimit = handler1.getMethodAnnotation(AccessLimit.class);
//            3.1 : 不包含此注解,则不进行操作
            if (accessLimit != null) {
//                3.2 : 判断请求是否受限制
                if (isLimit(request, accessLimit)) {
                    render(response, "{\"code\":\"30001\",\"message\":\"请求过快\"}");
                    return false;
                }
            }
        }
        return true;
    }

    //判断请求是否受限
    public boolean isLimit(HttpServletRequest request, AccessLimit accessLimit) {
        // 受限的redis 缓存key ,因为这里用浏览器做测试,我就用sessionid 来做唯一key,如果是app ,可以使用 用户ID 之类的唯一标识。
        String limitKey = request.getServletPath() + request.getSession().getId();
        // 从缓存中获取,当前这个请求访问了几次
        Integer redisCount = (Integer) redisTemplate.opsForValue().get(limitKey);
        if (redisCount == null) {
            //初始 次数
            redisTemplate.opsForValue().set(limitKey, 1, accessLimit.seconds(), TimeUnit.SECONDS);
            System.out.println("写入redis --");
        } else {
            System.out.println("intValue-->" + redisCount.intValue());
            if (redisCount.intValue() >= accessLimit.maxCount()) {
                return true;
            }
            // 次数自增
            redisTemplate.opsForValue().increment(limitKey);
        }
        return false;
    }

    private void render(HttpServletResponse response, String cm) throws Exception {
        response.setContentType("application/json;charset=UTF-8");
        OutputStream out = response.getOutputStream();
        out.write(cm.getBytes("UTF-8"));
        out.flush();
        out.close();
    }
}
  1. 注册拦截器
package com.example.interceptor;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.io.Serializable;

/**
 * @author lep
 * @date 2022-08-05 15:52
 */
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private FangshuaInterceptor interceptor;


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor);
    }

    /**
     * 解决 redis 的编码统一方面的问题 (了解)
     *
     * @param connectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory) {
        //创建 redisTemplate 模版
        RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
        //设置 value 的转化格式和 key 的转化格式
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        //关联 redisConnectionFactory
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }
}
  1. OK , 下面我们就可以在需要进行现在访问次数的controller中的方法使用该注解了
/**
 * @author lep
 * @date 2022-01-05 17:19
 */
@RestController
@RequestMapping("/test")
public class Test {


    @GetMapping("/test1")
//    指定此接口同一个用户在20秒内只能访问2次
    @AccessLimit(seconds = 20, maxCount = 2)
    public String test1() {
        return "我是test1";
    }
}
  1. 效果 . 当我们在20秒内多次请求该接口时 ,就会出现以下响应
    请添加图片描述
  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
可以通过使用拦截器或者过滤器实现对指定接口访问次数限制。 以下是使用拦截器实现限制访问次数的示例代码: 1. 创建自定义注解 `@AccessLimit`,用于标记需要限制访问次数接口。 ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AccessLimit { // 默认访问次数限制为5次 int limit() default 5; // 时间段,单位为秒,默认为60秒 int seconds() default 60; } ``` 2. 创建拦截器 `AccessLimitInterceptor`,用于实现限制访问次数的逻辑。 ```java @Component public class AccessLimitInterceptor implements HandlerInterceptor { @Autowired private RedisTemplate<String, Object> redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 判断是否标注了@AccessLimit注解 if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; if (!handlerMethod.hasMethodAnnotation(AccessLimit.class)) { return true; } // 获取@AccessLimit注解 AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class); // 获取接口访问限制次数时间段 int limit = accessLimit.limit(); int seconds = accessLimit.seconds(); // 获取请求的IP地址和接口地址 String ipAddr = getIpAddress(request); String requestUrl = request.getRequestURI(); // 设置Redis中的Key String redisKey = String.format("%s_%s", ipAddr, requestUrl); // 判断Redis中是否存在Key ValueOperations<String, Object> valueOps = redisTemplate.opsForValue(); if (!redisTemplate.hasKey(redisKey)) { // 第一次访问,设置初始值 valueOps.set(redisKey, 1, seconds, TimeUnit.SECONDS); } else { // 已经访问过,进行访问次数限制判断 int count = (int) valueOps.get(redisKey); if (count >= limit) { // 超出访问次数限制,返回错误信息 response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("超出访问次数限制"); return false; } else { // 访问次数加1 valueOps.increment(redisKey, 1); } } } return true; } /** * 获取请求的IP地址 */ private String getIpAddress(HttpServletRequest request) { String ipAddr = request.getHeader("x-forwarded-for"); if (StringUtils.isBlank(ipAddr) || "unknown".equalsIgnoreCase(ipAddr)) { ipAddr = request.getHeader("Proxy-Client-IP"); } if (StringUtils.isBlank(ipAddr) || "unknown".equalsIgnoreCase(ipAddr)) { ipAddr = request.getHeader("WL-Proxy-Client-IP"); } if (StringUtils.isBlank(ipAddr) || "unknown".equalsIgnoreCase(ipAddr)) { ipAddr = request.getRemoteAddr(); } return ipAddr; } } ``` 3. 在需要限制访问次数接口上添加 `@AccessLimit` 注解。 ```java @RestController public class DemoController { @GetMapping("/demo") @AccessLimit public String demo() { return "Hello World!"; } } ``` 这样,每个IP地址在60秒内最多只能访问 `/demo` 接口5次。 注意:以上代码仅供参考,实际应用中还需要考虑并发访问、线程安全等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

欢乐少年1904

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值