通过redis实现10秒内访问2次
2021-07-11 本文已影响0人
engineer_tang
1. 定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface VisitLimit {
int expireTime() default 60; //单位是秒
int times() default 20; //可访问次数
}
2. 添加面向切面的处理拦截器
package com.tangqiao.rainlyblog.common.component;
import com.tangqiao.rainlyblog.common.annotations.CheckSubmitForm;
import com.tangqiao.rainlyblog.common.annotations.VisitLimit;
import com.tangqiao.rainlyblog.common.config.AppConstantConfig;
import com.tangqiao.rainlyblog.common.utils.IpUtils;
import com.tangqiao.rainlyblog.common.utils.RedisConstant;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
public class VisitLimitAspect {
@Autowired
private RedisUtils redisUtils;
@Autowired
private AppConstantConfig appConstantConfig;
/**
* 定义切入点:对要拦截的方法进行定义与限制,如包、类
*
* 1、execution(public * *(..)) 任意的公共方法
* 2、execution(* set*(..)) 以set开头的所有的方法
* 3、execution(* com.lingyejun.annotation.LoggerApply.*(..))com.lingyejun.annotation.LoggerApply这个类里的所有的方法
* 4、execution(* com.lingyejun.annotation.*.*(..))com.lingyejun.annotation包下的所有的类的所有的方法
* 5、execution(* com.lingyejun.annotation..*.*(..))com.lingyejun.annotation包及子包下所有的类的所有的方法
* 6、execution(* com.lingyejun.annotation..*.*(String,?,Long)) com.lingyejun.annotation包及子包下所有的类的有三个参数,第一个参数为String类型,第二个参数为任意类型,第三个参数为Long类型的方法
* 7、execution(@annotation(com.lingyejun.annotation.Lingyejun))
*/
@Pointcut("@annotation(com.tangqiao.rainlyblog.common.annotations.VisitLimit)")
private void cutMethod() {
}
/**
* 前置通知:在目标方法执行前调用
*/
@Before("cutMethod()")
public void begin(JoinPoint joinPoint) throws NoSuchMethodException {
VisitLimit visitLimit = getDeclaredAnnotation(joinPoint);
long limitExpireTime = visitLimit.expireTime();
int limitTimes = visitLimit.times();
String ipAddr = IpUtils.getIpAddr();
String key = RedisConstant.BLOG_VISIT_LIMIT_PREFIX + ipAddr;
Long times = redisUtils.incr(key);
if(Objects.equals(times, 1L)) {
redisUtils.set(key, "1", limitExpireTime, TimeUnit.SECONDS);
} else {
if (times > limitTimes) {
throw new IllegalArgumentException("超过了访问次数");
}
}
}
/**
* 获取方法中声明的注解
*
* @param joinPoint
* @return
* @throws NoSuchMethodException
*/
public VisitLimit getDeclaredAnnotation(JoinPoint joinPoint) throws NoSuchMethodException {
// 获取方法名
String methodName = joinPoint.getSignature().getName();
// 反射获取目标类
Class<?> targetClass = joinPoint.getTarget().getClass();
// 拿到方法对应的参数类型
Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
// 根据类、方法、参数类型(重载)获取到方法的具体信息
Method objMethod = targetClass.getMethod(methodName, parameterTypes);
// 拿到方法定义的注解信息
return objMethod.getDeclaredAnnotation(VisitLimit.class);
}
}
3. IP获取工具类
package com.tangqiao.rainlyblog.common.utils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class IpUtils {
/**
* 获取当前网络ip
* @return
*/
public static String getIpAddr() {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = attr.getRequest();
String ipAddress = request.getHeader("x-forwarded-for");
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){
//根据网卡取本机配置的IP
InetAddress inet=null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress= inet.getHostAddress();
}
}
//对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15
if(ipAddress.indexOf(",")>0){
ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));
}
}
return ipAddress;
}
}
4. Redis常量类
public class RedisConstant {
public static final String BLOG_CATEGORY_PREFIX = "blog:category:detail:";
public static final String BLOG_VISIT_LIMIT_PREFIX = "blog:visit:limit:";
public static final String BLOG_CHECK_FORM_SUBMIT = "blog:check:form:submit:";
}
5. 在控制层的接口方法上加上注解
@VisitLimit(expireTime = 10, times = 2)
@ApiOperation("获取分类详细信息")
@GetMapping("/getDetail/{id}")
public ResultObject<TbCategory> getDetail(@PathVariable Long id) {
return ResultStateEnum.SUCCESS.toData(categoryService.getDetail(id));
// log.info("ip: {}", IpUtils.getIpAddr());
// log.info("login user: {}", getLoginUser());
// return ResultStateEnum.SUCCESS.message();
}
启动项目,然后连续访问该接口,可以看到如下效果:
image.png