前言
今天项目需要对接口进行限制访问次数(某个时间段只允许访问几次) 项目经理说是做成一个AOP 小组件的形式,便于以后其他项目复用,这次我们是用Redis+AOP 实现的。
实现逻辑:
使用请求头中的userid 来作为key 登陆次数作为value
请求时,判断Redis中是否有key ,if(没有) {新增key+value+定时时间}
if(有){判断value是否超出规定次数 if(超出){异常返回} if(未超出){ value+1 更新数据}}
1.自定义注解类
/**
* @Description: 接口限流自定义注解
* @author: xuxinku
* @Date: 2020/4/6 14:18
* @ModifiedDate:
* @Copyright:XX保险股份有限公司
*/
@Documented
@Target(ElementType.METHOD) //代表方法级别
@Inherited //是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessRestriction {
/**
* 多长时间内限制,默认 60
* @return
*/
long t () default 60;
/**
* 单位时间内能访问多少次,默认3次
* @return
*/
int count () default 3;
}
2.切面类实现
/**
* @Description: 接口限流使用
* @author: xuxinku
* @Date: 2020/4/6 14:18
* @ModifiedDate:
* @Copyright:江泰保险股份有限公司
*/
@Aspect
@Component
@Slf4j
public class AccessRestrictionAop {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Pointcut("@annotation(com.jtpf.es.aop.AccessRestriction)")
public void executeService() {
}
@Before("executeService()&&@annotation(accessRestriction)")
public void checkIP(JoinPoint joinPoint, AccessRestriction accessRestriction)throws Exception {
// 规则:不存在 赋初始值/存在且大于 抛出超时异常/ 次数+1
String key = LoginAop.getUserCode();
//根据key 查询是否存在 hasKey
String oldKay = redisTemplate.opsForValue().get(key);
// 不存在 赋初始值
if(StringUtils.isEmpty(oldKay)){
redisTemplate.opsForValue().set(key, "0", accessRestriction.t(), TimeUnit.SECONDS );
}
//存在且大于规定次数
int frequency = Integer.valueOf(redisTemplate.opsForValue().get(key)).intValue();
if (!StringUtils.isEmpty(oldKay) && frequency > accessRestriction.count()){
throw new BizException("当前用户已请求次数过多 请稍后重试");
}
//次数+1
int i = frequency +1;
//更新记录次数
redisTemplate.opsForValue().set(key, i+"",0 );
log.info("开始记录操作信息 ");
System.out.println("请求次数=====》"+oldKay);
System.out.println("key=====》"+key);
log.info("记录操作信息成功");
}
3.LoginAop工具
/**
* 必须添加这两个注解:Component: 泛指组件,讲该类纳入bean中;
* Aspect: 通过该注解及表达式就可以轻松的使用POJO来定义切面
* * @author tdh
*/
@Aspect
@Slf4j
@Component
public class LoginAop {
/**
* 获取用户编码
*
* @return
*/
public static String getUserCode() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String userCode = request.getHeader("jtpf.userId");
if (userCode == null) {
userCode = request.getParameter("jtpf.userId");
}
if (StringUtils.isBlank(userCode)) {
throw new ParamException("请重新登陆");
} else {
return userCode;
}
}
/**
* 获取分支机构编码
*
* @return
*/
public static String getBranchCode() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if (StringUtils.isNotBlank(request.getHeader("branchCode"))) {
return URLDecoder.decode(request.getHeader("branchCode"));
} else {
return null;
}
}
/**
* 获取总公司机构编码
*
* @return
*/
public static String getParentCode() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if (StringUtils.isNotBlank(request.getHeader("parentCode"))) {
return URLDecoder.decode(request.getHeader("parentCode"));
} else {
return null;
}
}
/**
* 获取用户名称
*
* @return
*/
public static String getUserName() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if (StringUtils.isNotBlank(request.getHeader("userName"))) {
return URLDecoder.decode(request.getHeader("userName"));
} else {
return null;
}
}
/**
* 定义拦截规则:拦截标有com.SheetChrist.annotation.Login类中注解的所有方法com.flow.controller.FlowController
*/
@Pointcut("execution(public * com.*.controller.*.*(..))")
public void loginMethodPointcut() {
}
@Around("loginMethodPointcut()")
public Object interceptor(ProceedingJoinPoint point) throws Throwable {
//返回的结果
Object result = null;
result = point.proceed();
return result;
}
public static String getIpAddr(){
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if ("0:0:0:0:0:0:0:1".equals(ip)) {
ip = "127.0.0.1";
}
if (ip.split(",").length > 1) {
ip = ip.split(",")[0];
}
return ip;
}
}
4.注解的使用
60秒 5次
@AccessRestriction(t =60, count = 5)
@GetMapping(value = "/testAOP2")
public void testAop2() {
System.out.println("限流测试===========结束》");
}
测试:
每天记录发现的问题,不进步就是在退步