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

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

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

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

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

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

背景

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

后端代碼可以在2個(gè)位置做控制:

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

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

二是放在AOP切面做:

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

實(shí)現(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 {

    /**
     * 過(guò)期時(shí)間,單位毫秒
     */
    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獲取方法對(duì)象
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //獲取方法上的@RepeatSubmit注解
        RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
        //獲取HttpServletRequest對(duì)象,以獲取請(qǐng)求uri
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String uri = request.getRequestURI();
        //拼接Redis的key,這里只是簡(jiǎn)單根據(jù)uri來(lái)判斷是否重復(fù)提交??梢愿鶕?jù)自己業(yè)務(wù)調(diào)整,比如根據(jù)用戶id或者請(qǐng)求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不可用,則打印日志記錄,但依然對(duì)請(qǐng)求放行
            log.error("", e);
            return joinPoint.proceed();
        }
        //Redis可用的情況,如果flag=true說(shuō)明單位時(shí)間內(nèi)這是第一次請(qǐng)求,放行
        if (flag) {
            return joinPoint.proceed();
        } else {
            //進(jìn)入else說(shuō)明單位時(shí)間內(nèi)進(jìn)行了多次請(qǐng)求,則攔截請(qǐng)求并提示稍后重試
            throw new ServiceException("系統(tǒng)繁忙,請(qǐng)稍后重試");
        }
    }
}

這里利用redisTemplate的setIfAbsent()實(shí)現(xiàn)的,如果存在就不能set成功,set的同時(shí)設(shè)置過(guò)期時(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) {
    	//....
    }

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

背景

忘記密碼后通過(guò)發(fā)送手機(jī)驗(yàn)證碼找回密碼的場(chǎng)景。因?yàn)槊堪l(fā)一條短信都需要收費(fèi),所以要控制發(fā)短信的頻率。

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

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

}

總結(jié)

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

相關(guān)文章

  • 使用Jedis線程池returnResource異常注意事項(xiàng)

    使用Jedis線程池returnResource異常注意事項(xiàng)

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

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

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

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

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

    Redis連接超時(shí)異常的處理方法

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

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

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

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

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

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

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

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

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

    Redis的常見(jiàn)四種部署方案

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

    淺談Redis安全策略

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

最新評(píng)論

白城市| 日土县| 乌兰浩特市| 措美县| 兴海县| 喀喇| 全椒县| 丹寨县| 衡阳市| 屯昌县| 万安县| 和平区| 磐石市| 昔阳县| 驻马店市| 道真| 安康市| 恭城| 万载县| 苍溪县| 教育| 县级市| 安陆市| 景泰县| 宁陵县| 岫岩| 大石桥市| 贺兰县| 青河县| 如东县| 辉县市| 额济纳旗| 高唐县| 政和县| 南部县| 津市市| 台州市| 盖州市| 镇康县| 科技| 杭锦旗|