自定义注解SpringBoot防重复提交AOP方法详解

本文主要是介绍自定义注解SpringBoot防重复提交AOP方法详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《自定义注解SpringBoot防重复提交AOP方法详解》该文章描述了一个防止重复提交的流程,通过HttpServletRequest对象获取请求信息,生成唯一标识,使用Redis分布式锁判断请求是否...

防重复提交流程

获取到当前的 HttpServletRequest 对象,并记录请求的地址、请求方式、拦截到的类名和方法名等信息。

通过 pjp.getArgs() 获取请求参数,并将参数转换成字符串,用于生成唯一标识。

根据请求的地址、参数、唯一标识等信息生成缓存键 cacheRepeatKey,用于作为重复提交判断的依据。

通过 pjp.getSignature().getMethod()method.getAnnotation(PreventRepeatSubmit.class) 获取被拦截的方法上的 PreventRepeatSubmit 注解,进而获取注解中配置的有效期时间。

使用 Redis 分布式锁来判断请求是否重复提交。调用 redisCache.setNxCacheObject() 方法,尝试向缓存中设置键值对,如果设置成功(返回值为 true),则证明没有重复提交。若设置失败(返回值为 false),则抛出 BusinessException 异常,表示重复提交。

如果没有重复提交,则执行目标方法,即 pjp.proceed(),并将其返回。

自定义注解SpringBoot防重复提交AOP方法详解

引入依赖

<!--切面-->
<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&lLFzFdtaIygt;
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--RabbitMQ 测试依赖-->
<dependency>
   <groupId>org.springframework.amqp</groupId>
   <artifactId>spring-rabbit-test</artifactId>
   <scope>test</scope>
</dependency>
<!-- 数据库-->
<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.*;

/**
 * 自定义注解防止表单重复提交
 *
 */
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PreventRepeatSubmit
{
    /**
     * 间隔时间(ms),小于此时间视为重复提交
     */
    public int interval() default 40;

    /**
     * 提示消息
     */
    public String message() default "不允许重复提交,请稍候再试";
}

切面

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);

    // 令牌自定义标识
    @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("防重复提交的请求地址:{} ,请求方式:{}",requestURI,request.getMethod());
        LOG.info("防重复提交拦截到的类名:{} ,方法:{}",pjp.getTarget().getClass().getSimpleName(),pjp.getSignature().getName());

        //获取请求参数
        Object[] args = pjp.getArgs();
        String argStr = JSON.toJSONString(args);
        //这里替换是为了在redis可视化工具中方便查看
        argStr=argStr.replace(":","#");
        // 唯一值(没有消息头则使用请求地址)
        String submitKey = request.getHeader(header).trim();
        // 唯一标识(指定key + url +参数+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);
        //reChina编程dis分布式锁
        Boolean aBoolean = redisCache.setNxCacheObject(cacheRepeatKey, 1, preventRepeatSubmit.interval(), TimeUnit.SECONDS);
        //aBoolean为true则证明没有重复提交
        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)
    {
 lLFzFdtaIy       redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @param unit 时间单位
     * @return true=设置成功;false=设置失败
     */
    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(kehttp://www.chinasem.cny);
    }

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

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    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数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @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 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Set
     *
     * @param key 缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    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中存入数据
     *
     * @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中的数据
     *
     * @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中的数据
     *
     * @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中的某条数据
     *
     * @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 RespandroidonseResult<>("插入成功");
    }

//    @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<>("插入成功");
//    }
}

测试

发送请求的请求头

自定义注解SpringBoot防重复提交AOP方法详解

请求体

自定义注解SpringBoot防重复提交AOP方法详解

第一次插入

自定义注解SpringBoot防重复提交AOP方法详解

第二次插入失败

自定义注解SpringBoot防重复提交AOP方法详解

redis缓存

自定义注解SpringBoot防重复提交AOP方法详解

改用restful风格

自定义注解SpringBoot防重复提交AOP方法详解

第一次请求

自定义注解SpringBoot防重复提交AOP方法详解

结果

自定义注解SpringBoot防重复提交AOP方法详解

第二次发起请求失败

自定义注解SpringBoot防重复提交AOP方法详解

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程China编程(www.chinasem.cn)。

这篇关于自定义注解SpringBoot防重复提交AOP方法详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1156420

相关文章

Springboot配置文件相关语法及读取方式详解

《Springboot配置文件相关语法及读取方式详解》本文主要介绍了SpringBoot中的两种配置文件形式,即.properties文件和.yml/.yaml文件,详细讲解了这两种文件的语法和读取方... 目录配置文件的形式语法1、key-value形式2、数组形式读取方式1、通过@value注解2、通过

Java 接口定义变量的示例代码

《Java接口定义变量的示例代码》文章介绍了Java接口中的变量和方法,接口中的变量必须是publicstaticfinal的,用于定义常量,而方法默认是publicabstract的,必须由实现类... 在 Java 中,接口是一种抽象类型,用于定义类必须实现的方法。接口可以包含常量和方法,但不能包含实例

JAVA Calendar设置上个月时,日期不存在或错误提示问题及解决

《JAVACalendar设置上个月时,日期不存在或错误提示问题及解决》在使用Java的Calendar类设置上个月的日期时,如果遇到不存在的日期(如4月31日),默认会自动调整到下个月的相应日期(... 目录Java Calendar设置上个月时,日期不存在或错误提示java进行日期计算时如果出现不存在的

Springboot的配置文件及其优先级说明

《Springboot的配置文件及其优先级说明》文章介绍了SpringBoot的配置文件,包括application.properties和application.yml的使用,以及它们的优先级,还讨... 目录配置文件内置配置文件yml与properties的比较优先级比较外置配置文件springboot

Java利用Spire.XLS for Java自动化设置Excel的文档属性

《Java利用Spire.XLSforJava自动化设置Excel的文档属性》一个专业的Excel文件,其文档属性往往能大大提升文件的可管理性和可检索性,下面我们就来看看Java如何使用Spire... 目录Spire.XLS for Java 库介绍与安装Java 设置内置的 Excel 文档属性Java

Java中的CompletableFuture核心用法和常见场景

《Java中的CompletableFuture核心用法和常见场景》CompletableFuture是Java8引入的强大的异步编程工具,支持链式异步编程、组合、异常处理和回调,介绍其核心用法,通过... 目录1、引言2. 基本概念3. 创建 CompletableFuture3.1. 手动创建3.2.

java中4种API参数传递方式统一说明

《java中4种API参数传递方式统一说明》在Java中,我们可以使用不同的方式来传递参数给方法或函数,:本文主要介绍java中4种API参数传递方式的相关资料,文中通过代码介绍的非常详细,需要的... 目录1. 概述2. 参数传递方式分类2.1 Query Parameters(查询参数)2.2 Path

SpringBoot整合 Quartz实现定时推送实战指南

《SpringBoot整合Quartz实现定时推送实战指南》文章介绍了SpringBoot中使用Quartz动态定时任务和任务持久化实现多条不确定结束时间并提前N分钟推送的方案,本文结合实例代码给大... 目录前言一、Quartz 是什么?1、核心定位:解决什么问题?2、Quartz 核心组件二、使用步骤1

Java线程池核心参数原理及使用指南

《Java线程池核心参数原理及使用指南》本文详细介绍了Java线程池的基本概念、核心类、核心参数、工作原理、常见类型以及最佳实践,通过理解每个参数的含义和工作原理,可以更好地配置线程池,提高系统性能,... 目录一、线程池概述1.1 什么是线程池1.2 线程池的优势二、线程池核心类三、ThreadPoolE

Springboot请求和响应相关注解及使用场景分析

《Springboot请求和响应相关注解及使用场景分析》本文介绍了SpringBoot中用于处理HTTP请求和构建HTTP响应的常用注解,包括@RequestMapping、@RequestParam... 目录1. 请求处理注解@RequestMapping@GetMapping, @PostMappin