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

自定義注解SpringBoot防重復(fù)提交AOP方法詳解

 更新時間:2025年12月11日 15:14:18   作者:無語堵上西樓  
該文章描述了一個防止重復(fù)提交的流程,通過HttpServletRequest對象獲取請求信息,生成唯一標(biāo)識,使用Redis分布式鎖判斷請求是否重復(fù)提交,并通過自定義注解和切面實現(xiàn)

防重復(fù)提交流程

獲取到當(dāng)前的 HttpServletRequest 對象,并記錄請求的地址、請求方式、攔截到的類名和方法名等信息。

通過 pjp.getArgs() 獲取請求參數(shù),并將參數(shù)轉(zhuǎn)換成字符串,用于生成唯一標(biāo)識。

根據(jù)請求的地址、參數(shù)、唯一標(biāo)識等信息生成緩存鍵 cacheRepeatKey,用于作為重復(fù)提交判斷的依據(jù)。

通過 pjp.getSignature().getMethod()method.getAnnotation(PreventRepeatSubmit.class) 獲取被攔截的方法上的 PreventRepeatSubmit 注解,進而獲取注解中配置的有效期時間。

使用 Redis 分布式鎖來判斷請求是否重復(fù)提交。調(diào)用 redisCache.setNxCacheObject() 方法,嘗試向緩存中設(shè)置鍵值對,如果設(shè)置成功(返回值為 true),則證明沒有重復(fù)提交。若設(shè)置失敗(返回值為 false),則拋出 BusinessException 異常,表示重復(fù)提交。

如果沒有重復(fù)提交,則執(zhí)行目標(biāo)方法,即 pjp.proceed(),并將其返回。

引入依賴

<!--切面-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
   <groupId>com.baomidou</groupId>
   <artifactId>mybatis-plus-boot-starter</artifactId>
   <version>3.5.1</version>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--RabbitMQ 測試依賴-->
<dependency>
   <groupId>org.springframework.amqp</groupId>
   <artifactId>spring-rabbit-test</artifactId>
   <scope>test</scope>
</dependency>
<!-- 數(shù)據(jù)庫-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>${mysql.version}</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.47</version>
</dependency>

properties配置

server.port=7125
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/itcast?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=root123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.pool-name=HikariCPDatasource
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=180000
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.timeout=10s
spring.redis.password=123
token.header=token

自定義注解

import java.lang.annotation.*;

/**
 * 自定義注解防止表單重復(fù)提交
 *
 */
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PreventRepeatSubmit
{
    /**
     * 間隔時間(ms),小于此時間視為重復(fù)提交
     */
    public int interval() default 40;

    /**
     * 提示消息
     */
    public String message() default "不允許重復(fù)提交,請稍候再試";
}

切面

import com.alibaba.fastjson.JSON;
import com.example.demo.annotation.PreventRepeatSubmit;
import com.example.demo.exception.BusinessException;
import com.example.demo.util.HttpCodeEnum;
import com.example.demo.util.RedisCache;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class PreventRepeatSubmitAspect {
    private static final Logger LOG = LoggerFactory.getLogger(PreventRepeatSubmitAspect.class);

    // 令牌自定義標(biāo)識
    @Value("${token.header}")
    private String header;

    @Autowired
    private RedisCache redisCache;

    // 定義一個切入點
    @Pointcut("@annotation(com.example.demo.annotation.PreventRepeatSubmit)")
    public void preventRepeatSubmit() {

    }

    @Around("preventRepeatSubmit()")
    public Object checkPrs(ProceedingJoinPoint pjp) throws Throwable {
        LOG.info("進入preventRepeatSubmit切面");
        //得到request對象
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String requestURI = request.getRequestURI();
        LOG.info("防重復(fù)提交的請求地址:{} ,請求方式:{}",requestURI,request.getMethod());
        LOG.info("防重復(fù)提交攔截到的類名:{} ,方法:{}",pjp.getTarget().getClass().getSimpleName(),pjp.getSignature().getName());

        //獲取請求參數(shù)
        Object[] args = pjp.getArgs();
        String argStr = JSON.toJSONString(args);
        //這里替換是為了在redis可視化工具中方便查看
        argStr=argStr.replace(":","#");
        // 唯一值(沒有消息頭則使用請求地址)
        String submitKey = request.getHeader(header).trim();
        // 唯一標(biāo)識(指定key + url +參數(shù)+token)
        String cacheRepeatKey = "repeat_submit:" + requestURI+":" +argStr+":"+ submitKey;
        MethodSignature ms = (MethodSignature) pjp.getSignature();
        Method method=ms.getMethod();
        PreventRepeatSubmit preventRepeatSubmit=method.getAnnotation(PreventRepeatSubmit.class);
        int interval = preventRepeatSubmit.interval();
        LOG.info("獲取到preventRepeatSubmit的有效期時間"+interval);
        //redis分布式鎖
        Boolean aBoolean = redisCache.setNxCacheObject(cacheRepeatKey, 1, preventRepeatSubmit.interval(), TimeUnit.SECONDS);
        //aBoolean為true則證明沒有重復(fù)提交
        if(!aBoolean){
            //JSON.toJSONString(ResponseResult.errorResult(HttpCodeEnum.SYSTEM_ERROR.getCode(),annotation.message())));
            throw new BusinessException(HttpCodeEnum.REPEATE_ERROR);
        }
        return pjp.proceed();
    }

}

redis工具類

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * spring redis 工具類
 *
 **/
@Component
public class RedisCache
{
    @Autowired
    public RedisTemplate redisTemplate;

    //添加分布式鎖
    public <T> Boolean setNxCacheObject(final String key, final T value,long lt,TimeUnit tu)
    {
        return redisTemplate.opsForValue().setIfAbsent(key,value,lt,tu);
    }

    /**
     * 緩存基本的對象,Integer、String、實體類等
     *
     * @param key 緩存的鍵值
     * @param value 緩存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 緩存基本的對象,Integer、String、實體類等
     *
     * @param key 緩存的鍵值
     * @param value 緩存的值
     * @param timeout 時間
     * @param timeUnit 時間顆粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 設(shè)置有效時間
     *
     * @param key Redis鍵
     * @param timeout 超時時間
     * @return true=設(shè)置成功;false=設(shè)置失敗
     */
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 設(shè)置有效時間
     *
     * @param key Redis鍵
     * @param timeout 超時時間
     * @param unit 時間單位
     * @return true=設(shè)置成功;false=設(shè)置失敗
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 獲取有效時間
     *
     * @param key Redis鍵
     * @return 有效時間
     */
    public long getExpire(final String key)
    {
        return redisTemplate.getExpire(key);
    }

    /**
     * 判斷 key是否存在
     *
     * @param key 鍵
     * @return true 存在 false不存在
     */
    public Boolean hasKey(String key)
    {
        return redisTemplate.hasKey(key);
    }

    /**
     * 獲得緩存的基本對象。
     *
     * @param key 緩存鍵值
     * @return 緩存鍵值對應(yīng)的數(shù)據(jù)
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 刪除單個對象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }

    /**
     * 刪除集合對象
     *
     * @param collection 多個對象
     * @return
     */
    public boolean deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection) > 0;
    }

    /**
     * 緩存List數(shù)據(jù)
     *
     * @param key 緩存的鍵值
     * @param dataList 待緩存的List數(shù)據(jù)
     * @return 緩存的對象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 獲得緩存的list對象
     *
     * @param key 緩存的鍵值
     * @return 緩存鍵值對應(yīng)的數(shù)據(jù)
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 緩存Set
     *
     * @param key 緩存鍵值
     * @param dataSet 緩存的數(shù)據(jù)
     * @return 緩存數(shù)據(jù)的對象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
    {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext())
        {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 獲得緩存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key)
    {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 緩存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
    {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 獲得緩存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key)
    {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 往Hash中存入數(shù)據(jù)
     *
     * @param key Redis鍵
     * @param hKey Hash鍵
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
    {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 獲取Hash中的數(shù)據(jù)
     *
     * @param key Redis鍵
     * @param hKey Hash鍵
     * @return Hash中的對象
     */
    public <T> T getCacheMapValue(final String key, final String hKey)
    {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }

    /**
     * 獲取多個Hash中的數(shù)據(jù)
     *
     * @param key Redis鍵
     * @param hKeys Hash鍵集合
     * @return Hash對象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 刪除Hash中的某條數(shù)據(jù)
     *
     * @param key Redis鍵
     * @param hKey Hash鍵
     * @return 是否成功
     */
    public boolean deleteCacheMapValue(final String key, final String hKey)
    {
        return redisTemplate.opsForHash().delete(key, hKey) > 0;
    }

    /**
     * 獲得緩存的基本對象列表
     *
     * @param pattern 字符串前綴
     * @return 對象列表
     */
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }
}

controller

import com.example.demo.annotation.PreventRepeatSubmit;
import com.example.demo.mapper.StuMapper;
import com.example.demo.model.ResponseResult;
import com.example.demo.model.Student;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;


@RestController
@RequestMapping("/test")
@Validated
public class TestController {

    @Resource
    private StuMapper stuMapper;

    @PostMapping("/user")
    @PreventRepeatSubmit
    public ResponseResult<String> user(@RequestBody Student student) {
        stuMapper.insert(student);
        return new ResponseResult<>("插入成功");
    }

//    @PostMapping("/user/{name}/{age}")
//    @PreventRepeatSubmit
//    public ResponseResult<String> user( @PathVariable String name ,@PathVariable Integer age) {
//        Student student = new Student(name,age);
//        stuMapper.insert(student);
//        return new ResponseResult<>("插入成功");
//    }
}

測試

發(fā)送請求的請求頭

請求體

第一次插入

第二次插入失敗

redis緩存

改用restful風(fēng)格

第一次請求

結(jié)果

第二次發(fā)起請求失敗

總結(jié)

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

相關(guān)文章

  • Spring?Security權(quán)限管理實現(xiàn)接口動態(tài)權(quán)限控制

    Spring?Security權(quán)限管理實現(xiàn)接口動態(tài)權(quán)限控制

    這篇文章主要為大家介紹了Spring?Security權(quán)限管理實現(xiàn)接口動態(tài)權(quán)限控制,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-06-06
  • 使用jpa的時候set實體類屬性自動持久化的解決方案

    使用jpa的時候set實體類屬性自動持久化的解決方案

    這篇文章主要介紹了使用jpa的時候set實體類屬性自動持久化的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • JAVA中String類的使用方法示例講解

    JAVA中String類的使用方法示例講解

    在Java開發(fā)中,String類作為操作字符串的核心工具,其功能強大且應(yīng)用廣泛,這篇文章主要介紹了JAVA中String類使用方法的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2026-05-05
  • Java集成swagger文檔組件

    Java集成swagger文檔組件

    這篇文章主要介紹了Java集成swagger文檔組件,通過詳細的圖文介紹和代碼分析展示,從頭展開說明詳細的過程,希望對你的開發(fā)有所幫助
    2021-06-06
  • 關(guān)于JDK源碼中的@author unascribed注釋閑談

    關(guān)于JDK源碼中的@author unascribed注釋閑談

    這篇文章主要介紹了關(guān)于JDK源碼中的@author unascribed注釋閑談,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • java接口自動化測試框架及斷言詳解

    java接口自動化測試框架及斷言詳解

    這篇文章主要介紹了java接口自動化測試框架及斷言詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-07-07
  • 淺談java二進制、十進制、十六進制、字符串之間的相互轉(zhuǎn)換

    淺談java二進制、十進制、十六進制、字符串之間的相互轉(zhuǎn)換

    下面小編就為大家?guī)硪黄獪\談二進制、十進制、十六進制、字符串之間的相互轉(zhuǎn)換。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考,一起跟隨小編過來看看吧
    2016-06-06
  • Java項目實現(xiàn)五子棋小游戲

    Java項目實現(xiàn)五子棋小游戲

    這篇文章主要為大家詳細介紹了Java項目實現(xiàn)五子棋小游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-05-05
  • 解決Map集合使用get方法返回null拋出空指針異常問題

    解決Map集合使用get方法返回null拋出空指針異常問題

    這篇文章主要介紹了解決Map集合使用get方法返回null拋出空指針異常問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 自定義application.yml配置項方式

    自定義application.yml配置項方式

    這篇文章主要介紹了自定義application.yml配置項方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-07-07

最新評論

永登县| 定西市| 宁安市| 静安区| 兴安县| 武安市| 腾冲县| 大理市| 息烽县| 嘉荫县| 绵阳市| 咸宁市| 逊克县| 黄大仙区| 盐城市| 吕梁市| 博客| 澜沧| 崇左市| 丰县| 娱乐| 怀柔区| 章丘市| 新泰市| 芷江| 许昌县| 柯坪县| 呼玛县| 二连浩特市| 乌兰察布市| 来凤县| 永丰县| 普格县| 夹江县| 平江县| 阳谷县| 东阳市| 澳门| 奇台县| 南部县| 吉林省|