Java - Redis之接口限流

Java - Redis之接口限流

应用场景:用户注册,电商秒杀接口,高并发 ....

1.创建自定义注解

import java.lang.annotation.*;

/**
 * @author Judge
 * @date 2020/11/5 14:16
 */


@Inherited
@Documented
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {

    /**
     * 标识限制次数
     *
     * @return
     */
    int limit() default 5;

    /**
     * 标识时间
     *
     * @return
     */
    int sec() default 5;

}


2.创建拦截器并通过反射对接口的访问进行限制

 
import com.ccl.coding.sso.annotation.AccessLimit;
import com.ccl.coding.sso.exception.AccessLimitException;
import lombok.extern.slf4j.Slf4j;
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.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
 
/**
 * 创建拦截器类实现HandlerInterceptor 接口
 */
@Slf4j
@Component
public class AccessLimitInterceptor extends HandlerInterceptorAdapter {
 
    @Autowired
    private RedisTemplate redisTemplate;
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handle)
            throws Exception {
        //判断handle是否为方法handle
        if (handle instanceof HandlerMethod) {
            //是的话转为HandleMethod
            HandlerMethod handleMethod = (HandlerMethod) handle;
            //获取方法
            Method method = handleMethod.getMethod();
            //判断该方法上是否有自定义注解@AccessLimit
            if (!method.isAnnotationPresent(AccessLimit.class)) {
                //不存在该注解则放行
                return super.preHandle(request, response, handle);
            }
            //获取自定义注解对象
            AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);
            if (accessLimit == null) {
                //放行
                return super.preHandle(request, response, handle);
            }
            //获取注解属性值
            int limit = accessLimit.limit();
            int sec = accessLimit.sec();
            //可将用户的id加上  限制每一个用户只能访问几次
            String key = request.getRequestURI();
            log.info("key : {}", key);
            //从redis中获取记录
            Integer maxLimit = (Integer) redisTemplate.opsForValue().get(key);
            if (maxLimit == null) {
                //第一次,计数器设置为1,设置redis过期时间
                redisTemplate.opsForValue().set(key, 1, sec, TimeUnit.SECONDS);
            } else if (maxLimit < limit) {
                //计数器加1
                redisTemplate.opsForValue().set(key, maxLimit + 1, sec, TimeUnit.SECONDS);
            } else {
//BusinessException为自义定异常类
 response.setContentType("text/html; charset=utf-8");
//                throw new BusinessException("访问频繁,客官休息一会哦!");
//                response.getWriter().write(JSON.toJSONString(new ResponseVo(-1, "访问频繁,客官休息一会哦!")));
                response.getWriter().write(JSON.toJSONString(new ResponseVo(-1, "访问频繁,客官休息一会哦!")));
                return false;            }
        }
        return super.preHandle(request, response, handle);
    }
 
}


对访问的资源进行拦截


 
import com.ccl.coding.sso.interceptor.AccessLimitInterceptor;
import com.ccl.coding.sso.interceptor.AuthCheckInterceptor;
import com.ccl.coding.sso.interceptor.RequestContextInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
/**
 * @author CHANG
 * @date 2019/7/28 11:01
 */
@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer {
 
    @Autowired
    private AuthCheckInterceptor authCheckInterceptor;
 
    @Autowired
    private AccessLimitInterceptor accessLimitInterceptor;
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
 
        registry.addInterceptor(new RequestContextInterceptor()).addPathPatterns("/**");
        registry.addInterceptor(authCheckInterceptor).addPathPatterns("/**");
        registry.addInterceptor(accessLimitInterceptor).addPathPatterns("/**");
    }
}


自定义异常类:


import lombok.Data;


/**
 * 业务系统 RuntimeException
 * 业务异常, 需要对外抛错。
 * 1. 接管异常信息
 * 2. 单据信息与后台信息交互
 * 3. 业务错误进行回滚
 *
 * @author qingjiaqi
 * @date 2019-09-10 09:58
 */
@Data
public class BusinessException extends RuntimeException {


    public int code;

    private String message;


    /**
     * 默认code
     */
    public BusinessException() {
        super();
        this.code = CommonErrCode.BUSINESS.getCode();
    }


    /**
     * 默认code码
     *
     * @param message
     */
    public BusinessException(String message) {
        super(message);
        this.code = CommonErrCode.BUSINESS.code;
        this.message = message;
    }

    /**
     * 指定错误码
     *
     * @param code
     * @param message
     */
    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
        this.message = message;
    }

    public BusinessException(CommonErrCode errCode) {
        super(errCode.getMsg());
        this.code = errCode.getCode();
        this.message = errCode.getMsg();
    }


}



异常码:

/**
 * 异常类
 *
 * @author qingjiaqi
 * @date 2019-09-10 09:41
 */
public enum CommonErrCode {

    NONE(0, "OK"),

    LOGIN_INVALID(300000, "登录失效"),

    REPEAT_LOGIN(300001, "您的账号在其他地方登录,如非本人操作,建议修改用户密码!"),

    UN_LOGIN(300002, "未登陆"),

    UN_REGISTER(300003, "未注册"),

    ARGS_INVALID(400000, "请求参数有误"),

    UNPACK_ERROR(400001, "报文解析失败"),

    SIGN_INVALID(400010, "验签失败"),

    DECRYPT_ERROR(400020, "解密失败"),

    ACCESS_TOKEN_ERROR(400030, "获取 ACESS_TOKEN 失败"),

    AUTH_TOKEN_INVALID(400300, "访问令牌非法"),

    NO_PERMISSION(400310, "没有访问权限"),

    NO_DATA_FOUND(400400, "没有数据"),

    NETWORK_ERROR(400500, "网络通讯故障"),

    BUSINESS(400600, "业务处理异常"),

    BAD_REQUEST(410000, "请求被拒绝"),

    SERVICE_NOT_EXISTS(420000, "服务不存在"),

    SERVICE_UNAVAILABLE(420001, "服务不可用"),

    SERVICE_ALREADY_EXISTS(420002, "服务已存在"),

    INTERNAL_SERVER_ERROR(500000, "服务器内部错误"),

    SERVICE_INVOKE_ERROR(500100, "服务调用出错"),

    DB_ERROR(500200, "数据库错误"),

    UNKNOW_ERROR(999999, "网络超时或未知异常"),

    ERROR(999998, "网络超时或未知异常"),

    AUTHCODE_ERROR(400700, "获取openId失败");


    int code;
    String message;

    CommonErrCode(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return message;
    }
}



Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://qingjiaqi.com/2020/11/05/java-redis之接口限流

Buy me a cup of coffee ☕.