AOP与自定义注解实现接口防刷案例

文章目录

目录

前言

AOP

编写自定义注解

自定义切面(RequestLimitContract)

创建Controller进行测试


前言

自定义注解实现接口防刷案例,这个案例主要实现是通过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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值