侧边栏壁纸
  • 累计撰写 11 篇文章
  • 累计创建 13 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

SpringBoot+Redis 实现在一定时间内限制接口请求次数

看书范
2022-01-14 / 0 评论 / 0 点赞 / 1277 阅读 / 10065 字

1. 添加maven依赖

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

2. 创建注解类AccessLimit

package com.KSF.annotation;

import java.lang.annotation.*;

/**
 * <p>
 * 注解 访问限制
 * </p>
 *
 * @author KSF
 * @since 2022/1/10 14:03
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface AccessLimit {
    /**
     * 访问时间限制
     *
     * @return int
     * @author KSF
     * @since 2022/1/10 14:07
     */
    Seconds seconds();
    /**
     * 访问次数
     *
     * @return int
     * @author KSF
     * @since 2022/1/10 14:06
     */
    MaxCount maxCount();

    /**
     * 缓存时间 枚举
     */
    enum Seconds {
        /**
         * 5秒缓存时间
         */
        FIVE(5);
        private final Integer key;

        private Seconds(Integer key) {
            this.key = key;
        }

        public Integer getKey() {
            return this.key;
        }

    }

    /**
     * 最大请求次数 枚举
     */
    enum MaxCount {
        /**
         * 1次最大请求次数
         */
        ONE(1);
        private final Integer key;

        private MaxCount(Integer key) {
            this.key = key;
        }

        public Integer getKey() {
            return this.key;
        }

    }
}

3. 创建拦截器SessionInterceptor

package com.KSF.Aspect;

import com.alibaba.fastjson.JSON;
import com.KSF.annotation.AccessLimit;
import com.KSF.vo.WrapperResponse;
import lombok.extern.slf4j.Slf4j;
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.PrintWriter;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 访问拦截器
 * </p>
 *
 * @author KSF
 * @since 2022/1/10 14:15
 */
@Slf4j
@Component
public class SessionInterceptor extends HandlerInterceptorAdapter {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(handler instanceof HandlerMethod){
            String unknown = "unknown";
            HandlerMethod hm = (HandlerMethod) handler;
            //获取方法中的注解,看是否有该注解
            AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
            if (null != accessLimit) {
                int seconds = accessLimit.seconds().getKey();
                int maxCount = accessLimit.maxCount().getKey();

                //从redis中获取用户访问的次数
                // 有可能ip是代理的
                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();
                }
                String key = ip + hm.getMethod().getName();
                Integer count = (Integer) redisTemplate.opsForValue().get(key);
                if (count == null) {
                    //第一次访问
                    redisTemplate.opsForValue().set(key, 1, seconds, TimeUnit.SECONDS);
                } else if (count < maxCount) {
                    //加1
                    count = count + 1;
                    redisTemplate.opsForValue().set(key, count);
                } else {
                    //超出访问次数
                    log.error("请求访问过快 【ip => "  + ip + "】-【请求方法 => "+ hm.getMethod().getName() +"】 且在 " + seconds + " 秒内超过最大限制 => " + maxCount);
                    response.setCharacterEncoding("UTF-8");
                    response.setHeader("Content-Type", "application/json; charset=utf-8");
                    try (PrintWriter printWriter = response.getWriter()) {
                        printWriter.write(JSON.toJSONString(WrapperResponse.error(500, "操作太快了", "")));
                    }
                    return false;
                }
            }
        }
        return super.preHandle(request, response, handler);
    }
}

拦截器用到的通用返回实体

package com.KSF.vo;

import com.KSF.constant.ResponseType;
import lombok.Data;

import java.io.Serializable;

/**
 * 通用返回实体
 *
 * @author KSF
 */
@Data
public class WrapperResponse<T> implements Serializable {


    /**
     * 返回编码
     */
    private int code;

    /**
     * 返回消息类型
     */
    private String type;

    /**
     * 请求状态说明
     */
    private String message = "";

    /**
     * 数据
     */
    private T data;


    /**
     * success WrapperResponse
     *
     * @param data 数据对象
     * @param <T>  数据类型
     * @return WrapperResponse<T>
     */
    public static <T> WrapperResponse<T> success(T data) {
        return handlerWrapperResponse(0, "", data, ResponseType.SUCCESS.getType());
    }

    /**
     * info WrapperResponse
     *
     * @param data 数据对象
     * @param <T>  数据类型
     * @return WrapperResponse<T>
     */
    public static <T> WrapperResponse<T> info(int code, String message, T data) {
        return handlerWrapperResponse(code, message, data, ResponseType.INFO.getType());
    }

    /**
     * warning WrapperResponse
     *
     * @param data 数据对象
     * @param <T>  数据类型
     * @return WrapperResponse<T>
     */
    public static <T> WrapperResponse<T> warning(int code, String message, T data) {
        return handlerWrapperResponse(code, message, data, ResponseType.WARNING.getType());
    }

    /**
     * error WrapperResponse
     *
     * @param <T>  数据类型
     * @param code
     * @param data 数据对象
     * @return WrapperResponse<T>
     */
    public static <T> WrapperResponse<T> error(int code, String message, T data) {
        return handlerWrapperResponse(code, message, data, ResponseType.ERROR.getType());
    }

    /**
     * 自定义固定错误 异常类型
     * */
    public static <T> WrapperResponse<T> errorProne(String message) {
        return handlerWrapperResponse(-1, message, null, ResponseType.ERROR.getType());
    }

    private static <T> WrapperResponse<T> handlerWrapperResponse(int code, String message, T data, String responseType) {
        WrapperResponse<T> result = new WrapperResponse<>();
        result.setCode(code);
        result.setData(data);
        result.setMessage(message);
        result.setType(responseType);
        return result;
    }
}

返回实体用到的返回类型

package com.KSF.constant;

/**
 * 返回类型
 *
 * @author KSF
 */
public enum ResponseType {

    /**
     * info
     */
    INFO("info"),

    /**
     * success
     */
    SUCCESS("success"),

    /**
     * warning
     */
    WARNING("warning"),

    /**
     * error
     */
    ERROR("error");

    private String type;

    ResponseType(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }
}

4. 在项目中的WebMvcConfig配置中添加拦截器

package com.KSF.confg;

import com.KSF.Aspect.SessionInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Resource
    private SessionInterceptor interceptor;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/")
                .setCachePeriod(31536000);
    }

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

}

5. 在API接口上添加上刚刚的自定义注解

@GetMapping("/getUserInfo")
@AccessLimit(seconds = AccessLimit.Seconds.FIVE, maxCount = AccessLimit.MaxCount.ONE)
public String getUserInfo() {
    return "Welcome to Guang Zhou";
}

6. 注解使用的结果

注解使用效果

0

评论区