亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

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

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

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

背景

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

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

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

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

二是放在AOP切面做:

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

實(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 {

    /**
     * 過期時(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獲取方法對象
        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說明單位時(shí)間內(nèi)這是第一次請求,放行
        if (flag) {
            return joinPoint.proceed();
        } else {
            //進(jìn)入else說明單位時(shí)間內(nèi)進(jìn)行了多次請求,則攔截請求并提示稍后重試
            throw new ServiceException("系統(tǒng)繁忙,請稍后重試");
        }
    }
}

這里利用redisTemplate的setIfAbsent()實(shí)現(xiàn)的,如果存在就不能set成功,set的同時(shí)設(shè)置過期時(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ā)送手機(jī)驗(yàn)證碼找回密碼的場景。因?yàn)槊堪l(fā)一條短信都需要收費(fèi),所以要控制發(fā)短信的頻率。

比如,同一個手機(jī)號在3分鐘內(nèi)只能發(fā)送3次短信,超過3次后則提示用戶“短信發(fā)送過于頻繁,請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ī)號格式
        checkPhoneNumber(smsDTO.getPhoneNumber());
        
        //...其他驗(yàn)證
        
		//拼接Redis的key(key為手機(jī)號,以控制一個手機(jī)號有限時(shí)間內(nèi)容發(fā)送的次數(shù))
        String cacheKey = "sms:code:resetPwd:"+smsDTO.getPhoneNumber();
        //驗(yàn)證發(fā)送短信次數(shù),超過則攔截(閾值是3次,超時(shí)時(shí)間是3分鐘,重試時(shí)間是10分鐘)
        checkSendCount(cacheKey, THRESHOLD, TIMEOUT, RETRY_TIME);
        return smsService.sendMsg(smsDTO);
    }
    
    /**
     * 驗(yàn)證發(fā)送短信次數(shù),超過則攔截
     * 該方法用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ù),是否超過閾值
        if (count > threshold) {
            //超過則設(shè)置過期時(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)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Redis模糊key查詢兩種方式總結(jié)

    Redis模糊key查詢兩種方式總結(jié)

    Redis作為一款高性能的鍵值存儲系統(tǒng),具有快速讀寫的特點(diǎn),被廣泛應(yīng)用于分布式緩存、消息隊(duì)列等領(lǐng)域,這篇文章主要給大家介紹了關(guān)于Redis模糊key查詢兩種方式的相關(guān)資料,需要的朋友可以參考下
    2024-07-07
  • 分割超大Redis數(shù)據(jù)庫例子

    分割超大Redis數(shù)據(jù)庫例子

    這篇文章主要介紹了分割超大Redis數(shù)據(jù)庫例子,本文講解了分割的需求、分割的思路及分割實(shí)例,需要的朋友可以參考下
    2015-03-03
  • Redis中序列化的兩種實(shí)現(xiàn)

    Redis中序列化的兩種實(shí)現(xiàn)

    本文主要介紹了Redis中序列化的兩種實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-07-07
  • Redis中有序集合的內(nèi)部實(shí)現(xiàn)方式的詳細(xì)介紹

    Redis中有序集合的內(nèi)部實(shí)現(xiàn)方式的詳細(xì)介紹

    本文主要介紹了Redis中有序集合的內(nèi)部實(shí)現(xiàn)方式的詳細(xì)介紹,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • redis 限制內(nèi)存使用大小的實(shí)現(xiàn)

    redis 限制內(nèi)存使用大小的實(shí)現(xiàn)

    這篇文章主要介紹了redis 限制內(nèi)存使用大小的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • Redis 數(shù)據(jù)庫忘記密碼找回或重置的解決方法

    Redis 數(shù)據(jù)庫忘記密碼找回或重置的解決方法

    對于 Redis 數(shù)據(jù)庫,如果忘記了密碼,可以通過密碼重置來找回密碼,今天通過本文給大家分享Redis 數(shù)據(jù)庫忘記密碼找回或重置的解決方法,感興趣的朋友一起看看吧
    2024-01-01
  • Redis中三種特殊數(shù)據(jù)類型命令詳解

    Redis中三種特殊數(shù)據(jù)類型命令詳解

    Geospatial是地理位置類型,我們可以用來查詢附近的人、計(jì)算兩人之間的距離等,這篇文章主要介紹了Redis中三種特殊數(shù)據(jù)類型命令詳解,需要的朋友可以參考下
    2024-05-05
  • 淺談redis的過期時(shí)間設(shè)置和過期刪除機(jī)制

    淺談redis的過期時(shí)間設(shè)置和過期刪除機(jī)制

    本文主要介紹了redis的過期時(shí)間設(shè)置和過期刪除機(jī)制,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Windows下Redis的安裝使用圖解

    Windows下Redis的安裝使用圖解

    Redis是一個key-value存儲系統(tǒng)。Redis的出現(xiàn),很大程度補(bǔ)償了memcached這類key/value存儲的不足,在部分場合可以對關(guān)系數(shù)據(jù)庫起到很好的補(bǔ)充作用。這篇文章小編為大家分享了在Windows下進(jìn)行安裝和使用Redis的技巧。
    2015-09-09
  • Redis瞬時(shí)高并發(fā)秒殺方案總結(jié)

    Redis瞬時(shí)高并發(fā)秒殺方案總結(jié)

    本文講述了Redis瞬時(shí)高并發(fā)秒殺方案總結(jié),具有很好的參考價(jià)值,感興趣的小伙伴們可以參考一下,具體如下:
    2018-05-05

最新評論