@Documented
@Inherited
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLimit {
int second() default 1;//规定秒数内
int maxCount() default 1;//最大请求次数
}
package com.cocolla.interceptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jeecg.common.api.vo.Result;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
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 com.cocolla.log.RequestLimit;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class RequestLimitIntercept extends HandlerInterceptorAdapter {
@Resource //根据name注入bean
private RedisTemplate<String,Object> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(handler.getClass().isAssignableFrom(HandlerMethod.class)){ //判断此class对象所表示的类或接口与指定的class参数所表示的类或接口是否相同
// HandlerMethod封装方法定义相关的信息,如类、方法、参数等
HandlerMethod handlerMethod= (HandlerMethod) handler;
Method method=handlerMethod.getMethod();
Object[] parameterValues = new Object[method.getParameterCount()];
// 获取参数名
LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
for (int i = 0; i < method.getParameterCount(); i++) {
String paramName = parameterNames[i];
String paramValue = request.getParameter(paramName);
parameterValues[i] = paramValue;
}
String name = getMD5Hash(parameterNames);
String value = getMD5Hash(parameterValues);
//获取方法中是否包含注解
RequestLimit methodAnnotation=method.getAnnotation(RequestLimit.class);
// 获取类中是否包含注解
RequestLimit classAnnotation=method.getDeclaringClass().getAnnotation(RequestLimit.class);
// 如果方法上有注解 就优先选择方法上的参数
RequestLimit requestLimit=methodAnnotation != null?methodAnnotation:classAnnotation;
if(requestLimit !=null){
if(isLimit(request,requestLimit,name+value)){
// respouseOut(response, Result.error(methodAnnotation.second()+"s内禁止重复提交"));
return false;
}
}
}
return super.preHandle(request, response, handler);
}
/**
* 判断请求是否受限
* */
private boolean isLimit(HttpServletRequest request, RequestLimit requestLimit,String valid) {
// 受限的 Redis 缓存 key 使用 方法参数 来做唯一 key
String limitKey = request.getServletPath() + valid;
System.out.println("limitKey:" + limitKey);
//使用了 Redis 的 increment 方法来对计数进行原子增加操作,确保多个线程同时访问时计数的准确性,只有当计数第一次从 Redis 中获取时才设置过期时间
Long redisCount = redisTemplate.opsForValue().increment(limitKey, 1);
if (redisCount == 1) {
// 设置过期时间
redisTemplate.expire(limitKey, requestLimit.second(), TimeUnit.SECONDS);
System.out.println("写入 Redis");
} else {
if (redisCount.intValue() >= requestLimit.maxCount()) {
return true;
}
}
return false;
}
/**
*回写给客户端
* */
private void respouseOut(HttpServletResponse response, Result<Object> result) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charaset=utf-8");
PrintWriter out=null;
String json= com.alibaba.fastjson.JSONObject.toJSON(result).toString();
out=response.getWriter();
out.append(json);
}
private static String getMD5Hash(Object[] arr) throws Exception {
String input = Arrays.toString(arr);
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] messageDigest = md.digest(input.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : messageDigest) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}