最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用Redis控制表單重復(fù)提交和控制接口訪問頻率方式

 更新時間:2025年06月20日 11:16:23   作者:pbxs  
這篇文章主要介紹了使用Redis控制表單重復(fù)提交和控制接口訪問頻率方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

場景一:控制表單重復(fù)提交

防重提交有很多方案,從前端的按鈕置灰,到后端synchronize鎖、Lock鎖、借助Redis語法實現(xiàn)簡單鎖、Redis+Lua分布式鎖、Redisson分布式鎖,再到DB的悲觀鎖、樂觀鎖、借助表唯一索引等等都可以實現(xiàn)防重提交,以保證數(shù)據(jù)的安全性。

這篇文章我們介紹其中一種方案–借助Redis語法實現(xiàn)簡單鎖,最終實現(xiàn)防重提交。

背景

我們項目中,為了控制表單重復(fù)提交問題,會在點擊頁面按鈕(向后端發(fā)起業(yè)務(wù)請求)后就會置灰按鈕,直到后端響應(yīng)后解除按鈕置灰。通過按鈕置灰來防止重啟提交問題。但Postman、Jmeter和其他服務(wù)調(diào)用(繞過前端頁面)呢?所以后端接口也要根據(jù)控制表單重復(fù)提交的問題。

后端代碼可以在2個位置做控制:

一是放在gateway網(wǎng)關(guān)做:

  • 好處是只在一個地方加上控制代碼,就可以控制所有接口的重復(fù)提交問題。
  • 壞處是控制的范圍太廣(比如查詢接口無需控制,控制了反而多余)、定義重復(fù)提交的時間段不能靈活調(diào)整。

二是放在AOP切面做:

  • 好處是只有需要的地方才會被控制(哪里需要引用一下自定義注解即可),另外也能靈活調(diào)整定義重復(fù)提交的時間段(自定義注解里定義時間字段開放給使用者填寫)。
  • 壞處是每個需要控制的地方都要加注解,會有侵入性和一定的工作量。

實現(xiàn)代碼

1、添加自定義注解

package com.xxx.annotations;

import java.lang.annotation.*;

/**
 * 自定義注解防止表單重復(fù)提交
 *
 * @Author WANGLINGQIANG
 * @Date 2023/9/6 10:11
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {

    /**
     * 過期時間,單位毫秒
     */
    long expireTime() default 500L;

}

2、添加AOP切面

package com.xxx.aop;

import com.xxx.annotations.RepeatSubmit;
import com.xxx.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
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.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.util.concurrent.TimeUnit;

/**
 * 防止表單重復(fù)提交切面
 *
 * @Author WANGLINGQIANG
 * @Date 2023/9/6 10:13
 */
@Slf4j
@Aspect
@Component
public class RepeatSubmitAspect {
    private static final String KEY_PREFIX = "repeat_submit:";
    @Resource
    private RedisTemplate redisTemplate;

    @Pointcut("@annotation(com.xxx.annotations.RepeatSubmit)")
    public void repeatSubmit() {}

    @Around("repeatSubmit()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    	//joinPoint獲取方法對象
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //獲取方法上的@RepeatSubmit注解
        RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
        //獲取HttpServletRequest對象,以獲取請求uri
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String uri = request.getRequestURI();
        //拼接Redis的key,這里只是簡單根據(jù)uri來判斷是否重復(fù)提交??梢愿鶕?jù)自己業(yè)務(wù)調(diào)整,比如根據(jù)用戶id或者請求token等
        String cacheKey = KEY_PREFIX.concat(uri);
        Boolean flag = null;
        try {
            //借助setIfAbsent(),key不存在才能設(shè)值成功
            flag = redisTemplate.opsForValue().setIfAbsent(cacheKey, "", annotation.expireTime(), TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            //如果Redis不可用,則打印日志記錄,但依然對請求放行
            log.error("", e);
            return joinPoint.proceed();
        }
        //Redis可用的情況,如果flag=true說明單位時間內(nèi)這是第一次請求,放行
        if (flag) {
            return joinPoint.proceed();
        } else {
            //進(jìn)入else說明單位時間內(nèi)進(jìn)行了多次請求,則攔截請求并提示稍后重試
            throw new ServiceException("系統(tǒng)繁忙,請稍后重試");
        }
    }
}

這里利用redisTemplate的setIfAbsent()實現(xiàn)的,如果存在就不能set成功,set的同時設(shè)置過期時間,可以是用使用默認(rèn),也可以自己根據(jù)業(yè)務(wù)調(diào)整。

另外,cacheKey的定義,也可以根據(jù)自己的需要去調(diào)整,比如根據(jù)當(dāng)前登錄用戶的userId、當(dāng)前登錄的token等。

3、使用

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

	@RepeatSubmit
    @PostMapping
    public AjaxResult add(@Validated @RequestBody SysUser user) {
    	//....
    }

場景二:控制接口調(diào)用頻率

背景

忘記密碼后通過發(fā)送手機驗證碼找回密碼的場景。因為每發(fā)一條短信都需要收費,所以要控制發(fā)短信的頻率。

比如,同一個手機號在3分鐘內(nèi)只能發(fā)送3次短信,超過3次后則提示用戶“短信發(fā)送過于頻繁,請10分鐘后再試”。

實現(xiàn)代碼

@Slf4j
@RestController
@RequestMapping("/sms")
public class SmsController {
    @Resource
    private ISmsService smsService;
    @Resource
    public RedisTemplate redisTemplate;

    @PostMapping("/sendValidCode")
    public Result sendValidCode(@RequestBody @Valid SmsDTO smsDTO) {
        //驗證手機號格式
        checkPhoneNumber(smsDTO.getPhoneNumber());
        
        //...其他驗證
        
		//拼接Redis的key(key為手機號,以控制一個手機號有限時間內(nèi)容發(fā)送的次數(shù))
        String cacheKey = "sms:code:resetPwd:"+smsDTO.getPhoneNumber();
        //驗證發(fā)送短信次數(shù),超過則攔截(閾值是3次,超時時間是3分鐘,重試時間是10分鐘)
        checkSendCount(cacheKey, THRESHOLD, TIMEOUT, RETRY_TIME);
        return smsService.sendMsg(smsDTO);
    }
    
    /**
     * 驗證發(fā)送短信次數(shù),超過則攔截
     * 該方法用lua腳本替換實現(xiàn)更好
     */
    private void checkSendCount(String cacheKey, Long threshold, Long timeout, String retryTime) {
   		//首先進(jìn)方法就先+1
        Long count = redisTemplate.opsForValue().increment(cacheKey);
        //然后比較次數(shù),是否超過閾值
        if (count > threshold) {
            //超過則設(shè)置過期時間為10分鐘,并提示10分鐘后重試
            redisTemplate.expire(cacheKey, 10L, TimeUnit.MINUTES);
            throw new ServiceException("短信發(fā)送過于頻繁,請" + retryTime + "分鐘后再試");
        } else {
            //沒超過3次,則累加上這一次
            redisTemplate.expire(cacheKey, timeout, TimeUnit.MINUTES);
        }
    }

}

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 使用Jedis線程池returnResource異常注意事項

    使用Jedis線程池returnResource異常注意事項

    這篇文章主要介紹了使用Jedis線程池returnResource異常注意事項,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-03-03
  • redis實現(xiàn)多級緩存同步方案詳解

    redis實現(xiàn)多級緩存同步方案詳解

    這篇文章主要介紹了redis實現(xiàn)多級緩存同步方案詳解,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-12-12
  • Redis+Caffeine如何構(gòu)建高性能二級緩存

    Redis+Caffeine如何構(gòu)建高性能二級緩存

    這篇文章主要介紹了Redis+Caffeine如何構(gòu)建高性能二級緩存問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-05-05
  • Redis連接超時異常的處理方法

    Redis連接超時異常的處理方法

    這篇文章主要給大家介紹了關(guān)于Redis連接超時異常的處理方法,文中通過示例代碼以及圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • 詳解Redis單線程架構(gòu)的優(yōu)勢與不足

    詳解Redis單線程架構(gòu)的優(yōu)勢與不足

    很多人都遇到過這么一道面試題:Redis是單線程還是多線程?這個問題既簡單又復(fù)雜,說他簡單是因為大多數(shù)人都知道Redis是單線程,說復(fù)雜是因為這個答案其實并不準(zhǔn)確,本文就給大家講講Redis單線程架構(gòu)的優(yōu)勢與不足,需要的朋友可以參考下
    2024-02-02
  • Redis內(nèi)存碎片處理實例詳解

    Redis內(nèi)存碎片處理實例詳解

    內(nèi)存碎片是redis服務(wù)中分配器分配存儲對象內(nèi)存的時產(chǎn)生的,下面這篇文章主要給大家介紹了關(guān)于Redis內(nèi)存碎片處理的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-05-05
  • 推薦一款神仙顏值的 Redis 客戶端工具(速收藏)

    推薦一款神仙顏值的 Redis 客戶端工具(速收藏)

    這篇文章主要給大家推薦一款神仙顏值的 Redis 客戶端工具(速收藏),非常好用的redis桌面管理工具,可以運行于Linux、Windows、Mac三大平臺,并且當(dāng)加載大數(shù)量的key不會crash,感興趣的朋友跟隨小編一起看看吧
    2020-12-12
  • Redis Stream秒殺系統(tǒng)實現(xiàn)

    Redis Stream秒殺系統(tǒng)實現(xiàn)

    文章瀏覽閱讀345次,點贊7次,收藏3次。高性能: Lua腳本原子操作,毫秒級響應(yīng)高并發(fā): 異步處理,支持10萬+ QPS數(shù)據(jù)一致性: 庫存不會超賣可靠性: 消息隊列確保訂單不丟失用戶體驗: 立即返回結(jié)果,無需等待。
    2025-11-11
  • Redis的常見四種部署方案

    Redis的常見四種部署方案

    這篇文章介紹Reids最為常見的四種部署模式,其實Reids和數(shù)據(jù)庫的集群模式差不多,可以分為 Redis單機模式部署、Redis主從模式部署、Redis哨兵模式部署、Cluster集群模式部署,其他的部署方式基本都是圍繞以下幾種方式在進(jìn)行調(diào)整到適應(yīng)的生產(chǎn)環(huán)境,感興趣的朋友一起看看吧
    2023-11-11
  • 淺談Redis安全策略

    淺談Redis安全策略

    Redis 提供了諸多安全策略,比如為了保證數(shù)據(jù)安全,提供了設(shè)置密碼的功能。本文就詳細(xì)的介紹了一下Redis安全策略,具有一定的參考價值,感興趣的可以了解一下
    2021-11-11

最新評論

元氏县| 宁国市| 凌海市| 化州市| 兴宁市| 循化| 都匀市| 江西省| 苍溪县| 拉孜县| 交城县| 烟台市| 陆丰市| 望奎县| 莲花县| 稷山县| 安阳市| 富裕县| 锡林浩特市| 微山县| 龙门县| 永济市| 长阳| 庄河市| 融水| 宁晋县| 泾川县| 惠东县| 苏尼特左旗| 新乐市| 昌平区| 兴义市| 寿宁县| 那坡县| 化德县| 白银市| 新乡县| 湾仔区| 微山县| 桑植县| 桃园市|