SpringBoot + Redis 实现接口限流分析

符号 阅读:247 2021-03-31 12:44:05 评论:0

前提说明: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(); 
	}

结果展示:

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

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

声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

发表评论
搜索
排行榜
关注我们

一个IT知识分享的公众号