redis限时业务应用(二)
每天点滴努力,成就编程路!
限时业务的应用;一般情景会是验证码,二维码生存周期,接口api防刷,订单重复提交问题。关于验证码(例如邮件验证码的有效时间),云之讯会有专门的验证api。订单重复提交问题,用用户id+订单商品信息id作为key值设置有效时间(极短)。接口防止刷,是本节讲述的重点。
自定义防止刷注解
加上此注解表示防刷
package com.sise.demo1.demo.common.config.annotation;
import java.lang.annotation.*;
/**
* author zxq
* date 2020/8/5 22:22
* 描述 自定义注解类
* fans
*/
@Inherited
@Documented
@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
//接口请求次数
int times() default 2;
//时间范围
int second() default 5;
}
定义拦截器
当加上@AccessLimit 便进行防刷限制。
package com.sise.demo1.demo.common.interceptor;
import com.sise.demo1.demo.common.config.annotation.AccessLimit;
import com.sise.demo1.demo.common.utils.IPUtils;
import com.sise.demo1.demo.common.utils.RequestUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* author zxq
* date 2020/8/5 22:29
* 拦截器:拦截在某一时间内,请求太多次。
*/
@Component
public class AccessLimitInterceptor implements HandlerInterceptor {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception{
try{
System.out.println("我被执行了 AccessLimitInterceptor");
if(handler instanceof HandlerMethod){
//强转
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取方法
Method method = handlerMethod.getMethod();
// 是否有AccessLimit注解
if(!method.isAnnotationPresent(AccessLimit.class)){
return true;
}
// 获取注解内容信息
AccessLimit accessLimit = method.getAnnotation(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)redisTemplate.opsForValue().get(key);
if(maxTimes == null){
//set时一定要加过期时间
redisTemplate.opsForValue().set(key, 1, second, TimeUnit.SECONDS);
}else if(maxTimes < times){
redisTemplate.opsForValue().set(key, maxTimes+1, second, TimeUnit.SECONDS);
}else{
// 30405 API_REQUEST_TOO_MUCH 请求过于频繁
RequestUtils.out(response,"请求过于频繁");
return false;
}
}
}catch (Exception e){
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
RequestUtils统一返回
package com.sise.demo1.demo.common.utils;
import com.alibaba.fastjson.JSONObject;
import javax.servlet.ServletResponse;
import java.io.PrintWriter;
/**
* author zxq
* date 2020/8/5 22:44
*/
public class RequestUtils {
public static void out(ServletResponse response, Object object) {
PrintWriter out = null;
try {
response.setContentType("application/json;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
JSONObject jsonObject = new JSONObject();
jsonObject.put("state",200);
jsonObject.put("msg","请求过于频繁");
jsonObject.put("data",null);
out = response.getWriter();
out.append(jsonObject.toString());
} catch (Exception e) {
System.out.println("exception");
} finally {
if (null != out) {
out.flush();
out.close();
}
}
}
}
应用拦截器
package com.sise.demo1.demo.common.config;
import com.sise.demo1.demo.common.interceptor.AccessLimitInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* author zxq
* date 2020/8/5 22:52
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
//这里需要注入拦截器 否则无法获取到拦截器注入的RedisTemplate<String, Integer> redisTemplate;
@Bean
public AccessLimitInterceptor accessLimitInterceptor(){
return new AccessLimitInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//API限流拦截
registry.addInterceptor(accessLimitInterceptor()).addPathPatterns("/**").excludePathPatterns("/static/**");
}
}
应用例子
运行截图
如有错误,或有更好的想法,请联系我,感激不尽!