SpringBoot + Redis 实现接口限流

前提说明:SpringBoot 集成封装Redis

功能说明:

使用了Java 注解+Redis实现对接口访问限流控制功能

代码说明:

自定义接口限流注解标签:

package com.zzg.common.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义限流注解
 * @author zzg
 *
 */
@Inherited
@Documented
@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
	 //指定second 时间内 API请求次数
    int times() default 4;

    // 请求次数的指定时间范围  秒数(redis数据过期时间)
    int second() default 10;
}

自定义拦截器解析接口限流注解标签:

package com.digipower.component.interceptor;

import java.io.OutputStream;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.alibaba.fastjson.JSON;
import com.digipower.common.annotation.AccessLimit;
import com.digipower.common.entity.Result;
import com.digipower.common.util.IpUtils;
import com.digipower.redis.util.RedisUtil;

/**
 * 接口防刷拦截器
 * @author zzg
 *
 */
@Component
public class FangshuaInterceptor extends HandlerInterceptorAdapter {
	 @Autowired
	 private RedisUtil redisUtil;
	 
	    @Override
	    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
	 
	        //判断请求是否属于方法的请求
	        if(handler instanceof HandlerMethod){
	 
	            HandlerMethod hm = (HandlerMethod) handler;
	 
	            //获取方法中的注解,看是否有该注解
	            AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
	            if(accessLimit == null){
	                return true;
	            }
	            int times = accessLimit.times();//请求次数
                int second = accessLimit.second();//请求时间范围
	            
	            //根据 IP + API 限流
                String key = IpUtils.getIpAddr(request) + request.getRequestURI();
                //根据key获取已请求次数
                Integer maxTimes = (Integer) redisUtil.get(key);
                
                if(maxTimes == null){
                    //set时一定要加过期时间
                	redisUtil.set(key, 1, second);
                }else if(maxTimes < times){
                	redisUtil.set(key, maxTimes+1, second);
                }else{
                	 //超出访问次数
                    render(response);
                    return false;
                }
	        }
	 
	        return true;
	 
	    }
	    private void render(HttpServletResponse response)throws Exception {
	        response.setContentType("application/json;charset=UTF-8");
	        OutputStream out = response.getOutputStream();
	        String str  = JSON.toJSONString(Result.error("接口请求过于频繁"));
	        out.write(str.getBytes("UTF-8"));
	        out.flush();
	        out.close();
	    }
}

涉及IP工具类:IpUtils源代码:

package com.zzg.common.util;

import javax.servlet.http.HttpServletRequest;

/**
 * IP 工具类
 * @author zzg
 *
 */
public class IpUtils {
	 /**
     * IpUtils工具类方法
     * 获取真实的ip地址
     * @param request
     * @return
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if(org.apache.commons.lang.StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){
            //多次反向代理后会有多个ip值,第一个ip才是真实ip
            int index = ip.indexOf(",");
            if(index != -1){
                return ip.substring(0,index);
            }else{
                return ip;
            }
        }
        ip = request.getHeader("X-Real-IP");
        if(org.apache.commons.lang.StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){
            return ip;
        }
        return request.getRemoteAddr();
    }


}

配置拦截器

package com.zzg.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.zzg.component.interceptor.FangshuaInterceptor;

@Configuration
@EnableWebMvc
public class SpringMVCConfig extends WebMvcConfigurerAdapter {
	/**
	 * 资源处理
	 */
	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
		registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
		// 资源图片映射(静态配置->基于数据库实现动态配置)
	    registry.addResourceHandler("/upload_file/**").addResourceLocations("file:F:/data/upload_file/");
		super.addResourceHandlers(registry);
	}
	
	// 限流拦截器
	@Bean
    public FangshuaInterceptor accessLimitInterceptor(){
        return new FangshuaInterceptor();
    }

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		// TODO Auto-generated method stub
		//API限流拦截
        registry.addInterceptor(accessLimitInterceptor()).addPathPatterns("/**").excludePathPatterns("/swagger-ui.html/**", "/swagger-resources/**", "/webjars/**", "/v2/api-docs/**");
	}
	
	
	
	
}

在项目的验证码接口使用接口限流注解:

/**
	 * 1、生成验证码
	 * @param httpServletRequest
	 * @param httpServletResponse
	 * @throws Exception
	 */
	@AccessLimit(times = 5, second=10)
	@RequestMapping("/defaultKaptcha")
	public void defaultKaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
			throws Exception {
		byte[] captchaChallengeAsJpeg = null;
		ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
		try {
			// 生产验证码字符串并保存到数据库中
			String createText = defaultKaptcha.createText();
			saveVerificationCode(createText);
			httpServletRequest.getSession().setAttribute("rightCode", createText);
			// 使用生产的验证码字符串返回一个BufferedImage对象并转为byte写入到byte数组中
			BufferedImage challenge = defaultKaptcha.createImage(createText);
			ImageIO.write(challenge, "jpg", jpegOutputStream);
		} catch (IllegalArgumentException e) {
			httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
			return;
		}

		// 定义response输出类型为image/jpeg类型,使用response输出流输出图片的byte数组
		captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
		httpServletResponse.setHeader("Cache-Control", "no-store");
		httpServletResponse.setHeader("Pragma", "no-cache");
		httpServletResponse.setDateHeader("Expires", 0);
		httpServletResponse.setContentType("image/jpeg");
		ServletOutputStream responseOutputStream = httpServletResponse.getOutputStream();
		responseOutputStream.write(captchaChallengeAsJpeg);
		responseOutputStream.flush();
		responseOutputStream.close();
	}

结果展示:

正常访问验证码接口,验证码图片现在正常输出.

SpringBoot + Redis 实现接口限流_第1张图片

使用F5 频繁刷新验证码接口,会得到如下提示信息:

SpringBoot + Redis 实现接口限流_第2张图片

你可能感兴趣的:(springBoot学习笔记,Java架构专栏)