文章目录
目录
前言
自定义注解实现接口防刷案例,这个案例主要实现是通过AOP和自定义注解实现。在实际的开发中很多时候一些复杂重复的操作我们,我们都可以使用注解+AOP的方式进行实现。今天我们主要讲接口防刷,其他的之后在进行实现。
准备工作
首先在做这个案例之前,希望大家对自定义注解有一定的了解。同时也要对Aop有一定的认知。
自定义注解不熟悉的可以看一下我之前发的一篇文章。
啊啊,终于搞明白了,原来注解是这么一回事。6000+字理解注解【一】
这里我是用到了Spring boot+Redis来进行实现的,其实也可以不适用Redis。大家可以自己发掘一下。
环境的搭建这里就不做过多的赘述了,都应该会建工程。
AOP
面向切面编程,一般都是对现有的代码做一些其他操作。比如追加日志了等功能都可以使用AOP进行实现。
在springBoot中使用AOP需要引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.5.7</version>
</dependency>
下面我们粗略说下AOP 的几个注解:
注解 作用:
@Aspect(切面) 通常是一个类,里面可以定义切入点和通知
@JointPoint(连接点) 程序执行过程中明确的点,一般是方法的调用
@Advice(通知) AOP在特定的切入点上执行的增强处理,有@before,@after,@afterReturning,@afterThrowing,@around
@Pointcut(切入点) 就是带有通知的连接点,在程序中主要体现为书写切入点表达式
这里不过多赘述AOP的知识。如有机会单独来讲AOP。
编写自定义注解
这里我们创建一个注解类,命名为RequestLimit
package com.onlyqi.upup01.annotation;
import org.springframework.core.annotation.Order;
import java.lang.annotation.*;
/**
* 自定义注解 限制访问
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
//最高优先级
@Order(1)
public @interface RequestLimit {
/*
* 允许访问的次数,默认值为max_value
* */
int count() default Integer.MAX_VALUE;
/*
* 以第一次请求往后退时间为准
* 例如第一次请求是在12:00点,请求时间设置一分钟
* 在12:01分的时间段内访问次数超过count会报错,但是到12:01之后会重新开始计数,而不是以后不可访问
* 时间段,单位为毫秒,默认值为一分钟
* 考虑到以上因素,如果知识为了避免攻击,建议把time设置大一点,例如一分钟20次
* 如果想要避免重复提交问题,需要且只能把Count设置成1,同时time设置小一点 例如一秒一次
*
* */
int time() default 1;
}
自定义切面(RequestLimitContract)
这里需要用到Redis所以需自行配置Redis的相关配置。
package com.onlyqi.upup01.aspect;
import com.onlyqi.upup01.annotation.RequestLimit;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
@Aspect
@Scope
@Component
public class RequestLimitContract {
@Autowired
private HttpServletResponse response;
@Autowired
private RedisTemplate redisTemplate;
@Before("within(@org.springframework.web.bind.annotation.RestController *) && @annotation(limit)")
public void requestLimit(final JoinPoint joinPoint, RequestLimit limit) throws IOException {
// System.out.println("1111111111111111111111111111111111111start");
// System.out.println("目标方法名为:" + joinPoint.getSignature().getName());
// System.out.println("目标方法所属类的简单类名:" + joinPoint.getSignature().getDeclaringType().getSimpleName());
// System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());
// System.out.println("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));
Object[] args = joinPoint.getArgs();
System.out.println(args+"1111111111111111111111111111111111111end");
HttpServletRequest request = null;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof HttpServletRequest) {
request = (HttpServletRequest) args[i];
break;
}
}
if (request == null) {
return;
}
String ip = request.getRemoteHost();
String url = request.getRequestURL().toString();
String key = "req_limit_".concat(url).concat(ip);
// redisTemplate.opsForValue().set("1","111111");
Long increment = redisTemplate.opsForValue().increment(key);
if (increment <= 1) {
redisTemplate.expire(key, limit.time(), TimeUnit.SECONDS);
}
if (increment > limit.count()) {
//throw new RuntimeException("访问平路太快,请过" + limit.time() + "秒后在访问");
output(response,"访问频率太快,请过" + limit.time() + "秒后在访问");
}
}
private void output(HttpServletResponse response, String msg) throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
ServletOutputStream os = null;
try {
os = response.getOutputStream();
os.write(msg.getBytes(StandardCharsets.UTF_8));
} finally {
if (os != null) {
os.flush();
os.close();
}
}
}
}
@scope:注解作用是值该类交给Spring管理之后是单实例的。
@within:用于匹配所有持有指定注解类型的方法;
@annotation(limit):是匹配含有limit注解的方法。
@@Before("within(@org.springframework.web.bind.annotation.RestController *) && @annotation(limit)"):表示对含有@RestController注解类下面的方法且含有limit的方法有效
jointPoint获取HttpServletRequest如果需要就可以加上。
Redis不熟悉的可以,只用两个方法即可:incr方法表示Key值加一,如果key值不存在创建一个Key值,且初始值为1.expire表示只要经过time时间key就会消失。
创建Controller进行测试
package com.onlyqi.upup01.controller;
import com.onlyqi.upup01.annotation.RequestLimit;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
public class TestController {
public int count=0;
@RequestLimit(count = 5,time = 2)
@RequestMapping("/test")
public String test(HttpServletRequest request ){
System.out.println("============================"+count);
count++;
return "测试页面";
}
}
在对应测试的接口上使用自定义注解。
yml配置:
server:
port: 8089
spring:
redis:
# redis数据库索引(默认为0),我们使用索引为3的数据库,避免和其他数据库冲突
database: 3
# redis服务器地址(默认为loaclhost)
host: 8.196.265.98
# redis端口(默认为6379)
port: 6379
启动项目:访问对应接口
正常速度访问,访问正常。
如果快速访问时,会进行拦截。
到这里案例就结束了,虽然功能简单,但学习的主要是思想。可以利用这种方式做其它功能实现。这就要看看你的想象力了。
瑞斯拜~~~~
————————————————
我是转载的:
原文链接:https://blog.csdn.net/weixin_46897073/article/details/122000190