SpringBoot+redis实现接口防刷

  1. 写一个RedisService,实现获取Redis 的set、get、incr(相当于计数器)

  2. 写@inferface注解类

  3. 做一个拦截器,因为要先于控制器判断

  4. 将拦截器注入Springboot


前言


一、引入依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
 <groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>

application.properties

spring.redis.port=6379
spring.redis.host=127.0.0.1
spring.redis.password=123456

二、使用步骤

2.1 RedisService操作redis

@Service
public class RedisService {
    private final StringRedisTemplate stringRedisTemplate;

    @Autowired
    public RedisService(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public void set(String key, String value) {
        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
        operations.set(key, value, 1, TimeUnit.MINUTES); // 设置值时设置过期时间为1分钟
    }

    public void set(String key, String value,int N) {
        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
        operations.set(key, value, N, TimeUnit.MINUTES); // 设置值时设置过期时间为N分钟
    }

    public String get(String key) {
        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
        return operations.get(key);
    }

    public Long incr(String key, long delta) {
        return performIncrement(key, delta);
    }

    public Long incr(String key) {
        return performIncrement(key, 1); // 使用默认增量 1
    }

    private Long performIncrement(String key, long delta) {
        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();

        // 检查键是否存在,如果不存在,则设置初始值为 0 并设置过期时间
        if (Boolean.FALSE.equals(stringRedisTemplate.hasKey(key))) {
            operations.set(key, "0", 1, TimeUnit.MINUTES); // 设置过期时间为 1 分钟
        }

        return operations.increment(key, delta);
    }
}

2.2 防刷的自定义注解

@Retention(RUNTIME)
@Target(METHOD)
public @interface AccessLimit {

    //每分钟
    int minutes();
    //访问的次数限制
    int maxCount();
//是否需要登录
    boolean needLogin()default true;
}

 

2.3 自定义的过滤器

过滤器主要是访问的时候 处理加了防刷注解的接口 然后限制访问给出不一样的返回结果

@Component
public class PreventRefreshInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private RedisService redisService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("进入拦截器");
        //判断请求是否属于方法的请求
        if(handler instanceof HandlerMethod){
            System.out.println("判断请求是否属于方法的请求");
            HandlerMethod hm = (HandlerMethod) handler;
            //获取方法中的注解,看是否有该注解
            AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
            if(accessLimit == null){
                return true;
            }
            //@AccessLimit(seconds=5, maxCount=5, needLogin=true)
            int minutes = accessLimit.minutes();
            int maxCount = accessLimit.maxCount();
            boolean login = accessLimit.needLogin();
            Map<String, Object> mp=new HashMap<>();
            mp.put("seconds",minutes);
            mp.put("maxCount",maxCount);
            mp.put("login",login);
            System.out.println(mp);
            String key = IPUtil.getIpAddr(request)+"_"+request.getRequestURI();
            log.error("Key:{}",key);
            //如果需要登录
//            if(login){
//                redisService.set(key, "1");
//            }
            //从redis中获取用户访问的次数
            String value = redisService.get(key);
            System.out.println("value:"+value);
            //判断value是否为null
            if(value == null){
                System.out.println("第1次访问,设置有效期"+minutes +"分钟");
                redisService.set(key, "1",minutes);
            }else{
                Long count = redisService.incr(key);
                System.out.println("第"+count+"次访问,设置有效期"+minutes +"分钟");
                System.out.println("{count :"+count + ", maxCount :"+maxCount+"}");
                if(count.intValue() > maxCount){
                    System.out.println("超出访问次数");
                    redisService.set(key, "超出次数");
                    renderError(response);
                }
            }
        }

        return true;

    }
    private void renderError(HttpServletResponse response)throws Exception {
        response.setContentType("application/json;charset=UTF-8");
        OutputStream out = response.getOutputStream();
        String str  = JSON.toJSONString(ResultT.error("error","超出访问次数"));
        out.write(str.getBytes("UTF-8"));
        out.flush();
        out.close();

    }
}

把修改后的过滤器给spring mvc的web过滤器

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private PreventRefreshInterceptor interceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        System.out.println("进入拦截器配置类");
        registry.addInterceptor(interceptor);
    }
}

2.4 测试的控制类 

@RestController
public class RefreshController {

    @AccessLimit(minutes=2, maxCount=10, needLogin=true)
    @RequestMapping("/shua")
    @ResponseBody
    public ResultT shua(){
        
        return ResultT.ok("请求成功");

    }

}

统一结果类

public class ResultT extends HashMap<String, Object> {
    public ResultT() {
        put("code", 0);
        put("msg", "success");
    }

    public static ResultT ok() {
        ResultT t = new ResultT();
        t.put("msg", "操作成功");
        return t;
    }

    public static ResultT ok(String msg) {
        ResultT t = new ResultT();
        t.put("msg", msg);
        return t;
    }

    public static ResultT ok(Map<String, Object> map) {
        ResultT t = new ResultT();
        map.putAll(t);
        return t;
    }

    public static ResultT error(String code,String msg) {

        ResultT t = new ResultT();
        t.put("code", code);
        t.put("msg", msg);
        return t;
    }

    public static ResultT error(String msg) {
        return error("500", msg);
    }
    public ResultT put(String key, Object value){
        super.put(key, value);
        return this;
    }
}

public class IPUtil {

    private static final String UNKNOWN = "unknown";

    protected IPUtil() {

    }

    /**
     * 获取 IP地址
     * 使用 Nginx等反向代理软件, 则不能通过 request.getRemoteAddr()获取 IP地址
     * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,
     * X-Forwarded-For中第一个非 unknown的有效IP字符串,则为真实IP地址
     */
    public static String getIpAddr(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();
        }
        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
    }

}

2.5 测试结果 

 


总结

  • 10
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值