本文主要是介绍自定义注解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(),并将其返回。

引入依赖
<!--切面--> <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<>("插入成功");
// }
}测试
发送请求的请求头

请求体

第一次插入

第二次插入失败

redis缓存

改用restful风格

第一次请求

结果

第二次发起请求失败

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