欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java中aop實(shí)現(xiàn)接口訪問(wèn)頻率限制

 更新時(shí)間:2023年04月19日 09:13:59   作者:YXXYX  
本文主要介紹了java中aop實(shí)現(xiàn)接口訪問(wèn)頻率限制,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

引言

項(xiàng)目開發(fā)中我們有時(shí)會(huì)用到一些第三方付費(fèi)的接口,這些接口的每次調(diào)用都會(huì)產(chǎn)生一些費(fèi)用,有時(shí)會(huì)有別有用心之人惡意調(diào)用我們的接口,造成經(jīng)濟(jì)損失;或者有時(shí)需要對(duì)一些執(zhí)行時(shí)間比較長(zhǎng)的的接口進(jìn)行頻率限制,這里我就簡(jiǎn)單演示一下我的解決思路;

主要使用spring的aop特性實(shí)現(xiàn)功能;

代碼實(shí)現(xiàn)

首先需要一個(gè)注解,找個(gè)注解可以理解為一個(gè)坐標(biāo),標(biāo)記該注解的接口都將進(jìn)行訪問(wèn)頻率限制;

package com.yang.prevent;
 
import java.lang.annotation.*;
 
/**
 * 接口防刷注解
 */
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Prevent {
 
    /**
     * 限制的時(shí)間值(秒)默認(rèn)60s
     */
    long value() default 60;

    /**
     * 限制規(guī)定時(shí)間內(nèi)訪問(wèn)次數(shù),默認(rèn)只能訪問(wèn)一次
     */
    long times() default 1;
 
    /**
     * 提示
     */
    String message() default "";
 
    /**
     * 策略
     */
    PreventStrategy strategy() default PreventStrategy.DEFAULT;
}

value就是限制周期,times是在一個(gè)周期內(nèi)訪問(wèn)次數(shù),message是訪問(wèn)頻率過(guò)多時(shí)的提示信息,strategy就是一個(gè)限制策略,是自定義的,如下:

package com.yang.prevent;

/**
 * 防刷策略枚舉
 */
public enum PreventStrategy {

    /**
     * 默認(rèn)(60s內(nèi)不允許再次請(qǐng)求)
     */
    DEFAULT
}

下面就是aop攔截的具體代碼:

package com.yang.prevent;

import com.yang.common.StatusCode;
import com.yang.constant.redis.RedisKey;
import com.yang.exception.BusinessException;
import com.yang.utils.IpUtils;
import org.aspectj.lang.JoinPoint;
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.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 防刷切面實(shí)現(xiàn)類
 */
@Aspect
@Component
public class PreventAop {

    @Resource
    private RedisTemplate<String, Long> redisTemplate;


    /**
     * 切入點(diǎn)
     */
    @Pointcut("@annotation(com.yang.prevent.Prevent)")
    public void pointcut() {}


    /**
     * 處理前
     */
    @Before("pointcut()")
    public void joinPoint(JoinPoint joinPoint) throws Exception {
        // 獲取調(diào)用者ip
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
        String userIP = IpUtils.getUserIP(httpServletRequest);
        // 獲取調(diào)用接口方法名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = joinPoint.getTarget().getClass().getMethod(
                methodSignature.getName(),
                methodSignature.getParameterTypes()); // 獲取該接口方法
        String methodFullName = method.getDeclaringClass().getName() + method.getName(); // 獲取到方法名
        Prevent preventAnnotation = method.getAnnotation(Prevent.class); // 獲取該接口上的prevent注解(為了使用該注解內(nèi)的參數(shù))
        // 執(zhí)行對(duì)應(yīng)策略
        entrance(preventAnnotation, userIP, methodFullName);
    }


    /**
     * 通過(guò)prevent注冊(cè)判斷執(zhí)行策略
     * @param prevent 該接口的prevent注解對(duì)象
     * @param userIP 訪問(wèn)該接口的用戶ip
     * @param methodFullName 該接口方法名
     */
    private void entrance(Prevent prevent, String userIP, String methodFullName) throws Exception {
        PreventStrategy strategy = prevent.strategy(); // 獲取校驗(yàn)策略
        if (Objects.requireNonNull(strategy) == PreventStrategy.DEFAULT) { // 默認(rèn)就是default策略,執(zhí)行default策略方法
            defaultHandle(userIP, prevent, methodFullName);
        } else {
            throw new BusinessException(StatusCode.FORBIDDEN, "無(wú)效的策略");
        }
    }


    /**
     * Default測(cè)試執(zhí)行方法
     * @param userIP 訪問(wèn)該接口的用戶ip
     * @param prevent 該接口的prevent注解對(duì)象
     * @param methodFullName 該接口方法名
     */
    private void defaultHandle(String userIP, Prevent prevent, String methodFullName) throws Exception {
        String base64StrIP = toBase64String(userIP); // 加密用戶ip(避免ip存在一些特殊字符作為redis的key不合法)
        long expire = prevent.value(); // 獲取訪問(wèn)限制時(shí)間
        long times = prevent.times(); // 獲取訪問(wèn)限制次數(shù)
        // 限制特定時(shí)間內(nèi)訪問(wèn)特定次數(shù)
        long count = redisTemplate.opsForValue().increment(
                RedisKey.PREVENT_METHOD_NAME + base64StrIP + ":" + methodFullName, 1); // 訪問(wèn)次數(shù)+1
        if (count == 1) { // 如果訪問(wèn)次數(shù)為1,則重置訪問(wèn)限制時(shí)間(即redis超時(shí)時(shí)間)
            redisTemplate.expire(
                    RedisKey.PREVENT_METHOD_NAME + base64StrIP + ":" + methodFullName,
                    expire,
                    TimeUnit.SECONDS);
        }
        if (count > times) { // 如果訪問(wèn)次數(shù)超出訪問(wèn)限制次數(shù),則禁止訪問(wèn)
            // 如果有限制信息則使用限制信息,沒(méi)有則使用默認(rèn)限制信息
            String errorMessage =
                    !StringUtils.isEmpty(prevent.message()) ? prevent.message() : expire + "秒內(nèi)不允許重復(fù)請(qǐng)求";
            throw new BusinessException(StatusCode.FORBIDDEN, errorMessage);
        }
    }


    /**
     * 對(duì)象轉(zhuǎn)換為base64字符串
     * @param obj 對(duì)象值
     * @return base64字符串
     */
    private String toBase64String(String obj) throws Exception {
        if (StringUtils.isEmpty(obj)) {
            return null;
        }
        Base64.Encoder encoder = Base64.getEncoder();
        byte[] bytes = obj.getBytes(StandardCharsets.UTF_8);
        return encoder.encodeToString(bytes);
    }
}

注釋寫的很清楚了,這里我簡(jiǎn)單說(shuō)一下關(guān)鍵方法defaultHandle:

1,首先加密ip,原因就是避免ip存在一些特殊字符作為redis的key不合法,該ip是組成redis主鍵的一部分,redis主鍵格式為:polar:prevent:加密ip:方法名

這樣就能區(qū)分不同ip,同一ip下區(qū)分不同方法的訪問(wèn)頻率;

2,expire和times都是從@Prevent注解中獲取的參數(shù),默認(rèn)是60s內(nèi)最多訪問(wèn)1次,可以自定義;

3,然后接口訪問(wèn)次數(shù)+1(該設(shè)備ip下),如果該接口訪問(wèn)次數(shù)為1,則說(shuō)明這是這個(gè)ip第一次訪問(wèn)該接口,或者是該接口的頻率限制已經(jīng)解除,即該接口訪問(wèn)次數(shù)+1前redis中沒(méi)有該ip對(duì)應(yīng)接口的限制記錄,所以需要重新設(shè)置對(duì)應(yīng)超時(shí)時(shí)間,表示新的一輪頻率限制開始;如果訪問(wèn)次數(shù)超過(guò)最大次數(shù),則禁止訪問(wèn)該接口,直到該輪頻率限制結(jié)束,redis緩存的記錄超時(shí)消失,才可以再次訪問(wèn)該接口;

這個(gè)方法理解了其他就不難了,其他方法就是給這個(gè)方法傳參或者作為校驗(yàn)或數(shù)據(jù)處理的;

下面測(cè)試一下,分別標(biāo)記兩個(gè)接口,一個(gè)使用@Prevent默認(rèn)參數(shù),一個(gè)使用自定義參數(shù):

image-20230212013539520

調(diào)用getToleById接口,意思是60s內(nèi)只能調(diào)用該接口1次:

第一次調(diào)用成功,redis鍵值為1

image-20230212014446080

image-20230212014507651

第二次失敗,需要等60s

image-20230212014328928

redis鍵值變成了2

image-20230212014419442

getRoleList是自定義參數(shù),意思是20s內(nèi)最多只能訪問(wèn)該接口5次:

未超出頻率限制

image-20230212014612633

image-20230212014635266

超出頻率限制

image-20230212014658229

image-20230212014707288

總體流程就是這樣了,aop理解好了不難,也比較實(shí)用,可以在自己項(xiàng)目中使用;

到此這篇關(guān)于java中aop實(shí)現(xiàn)接口訪問(wèn)頻率限制的文章就介紹到這了,更多相關(guān)java aop接口訪問(wèn)頻率限制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論