热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

自定义springbootstarter实现幂等注解防止重复提交

一般遇见这种需求,大体思路思路我想基本是这样的,1.自定义一个spring-boot-starter2.启动一个拦截器实现拦截自定义注解3.根据注解的一

一般遇见这种需求,大体思路思路我想基本是这样的,1.自定义一个spring-boot-starter2.启动一个拦截器实现拦截自定义注解3.根据注解的一些属性进行拼接一个key4.判断key是否存在4.1 不存在 存入redis,然后设置一个过期时间(一般过期时间也是注解的一个属性)4.2 存在则抛出一个重复提交异常 

 

闲话少说,先来一个使用端代码以及结果

使用方式

key = "T(cn.goswan.orient.common.security.util.SecurityUtils).getUser().getUsername()+#test.id"

这部分 的key就是拦截器里面用到的判断的key,具体可以根据自己业务用el表达式去定义

我用的是class fullpanth+用户名+业务主键 当作判定key

expireTime = 3

设置为了 3 

timeUnit = TimeUnit.SECONDS

设置为了秒,即为3秒后这个key从缓存中消失,使用端一定注意这个时常一定要大于自己的业务处理耗时

好了下面上结果,连续发送两次请求(postman 发送)第一次请求并没有报错

第二次请求抛出如下错误(自定义的错误)

exception.IdempotentException: classUrl public cn.goswan.orient.common.core.util.R com..demo.controller.TestController.save(com.demo.entity.Test) not allow repeat submit

好了,说了这么多,下面上源码


目录结构


pom 文件(这里的comm-data实际上内部是对redis 的引用配置可以忽略,大家可以替换成自己的redis 配置即可,如果有不明白的可以看看我之前的文件,redis templete 哨兵配置代码参考一下)


cn.goswanorient-common3.9.04.0.0basal-common-idempotentorg.redissonredisson-spring-boot-startercn.goswanorient-common-data


Idempotent.java

package com.basal.common.idempotent.annotation;import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;/*** @Author alan.wang* @date: 2021-12-30 17:54* @desc: 定义注解*/@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {/*** 幂等操作的唯一标识,使用spring el表达式 用#来引用方法参数* @return Spring-EL expression*/String key() default "";// /**
// * 是否作用域是所有请求(根据请求ip)
// * 默认:false
// * false:只做用在当前请求人(限定同意时间段只对当前访问ip拦截)
// * ture: 作用在所有人(同一时间对所有ip进行拦截)
// *
// * @return isWorkOnAll
// **/
// boolean isWorkOnAll() default false;/*** 有效期 默认:1 有效期要大于程序执行时间,否则请求还是可能会进来* @return expireTime*/int expireTime() default 1;/*** 时间单位 默认:s* @return TimeUnit*/TimeUnit timeUnit() default TimeUnit.SECONDS;
}


IdempotentAspect.java

package com.basal.common.idempotent.aspect;import cn.goswan.orient.common.data.util.StringUtils;
import com.basal.common.idempotent.annotation.Idempotent;
import com.basal.common.idempotent.exception.IdempotentException;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.Redisson;
import org.redisson.api.RMapCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;
import java.util.Objects;/*** @Author alan.wang* @date: 2021-12-30 17:56* @desc:* 防止重复提交注解拦截器,具体流程就是拦截带@Idempotent的方法,然后从redis取出key* 如果key 已经存在:抛出自定义异常* 如果key不存在:则存入*/
@Aspect
public class IdempotentAspect {final SpelExpressionParser PARSER = new SpelExpressionParser();final LocalVariableTableParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer();private static final String RMAPCACHE_KEY = "idempotent";@Autowiredprivate Redisson redisson;@Pointcut("@annotation(com.basal.common.idempotent.annotation.Idempotent)")public void pointCut() {}@Before("pointCut()")public void beforeCut(JoinPoint joinPoint) {//获取切面拦截的方法Object[] arguments = joinPoint.getArgs();Signature signature = joinPoint.getSignature();MethodSignature methodSignature = (MethodSignature) signature;if (!methodSignature.getMethod().isAnnotationPresent(Idempotent.class)) {return;}Method method = ((MethodSignature) signature).getMethod();if (method.getDeclaringClass().isInterface()) {try {method = joinPoint.getTarget().getClass().getDeclaredMethod(joinPoint.getSignature().getName(),method.getParameterTypes());} catch (SecurityException | NoSuchMethodException e) {throw new RuntimeException(e);}}//获取切面拦截的方法的参数并放入值context中StandardEvaluationContext context = new StandardEvaluationContext();String[] params = DISCOVERER.getParameterNames(method);if (params != null && params.length > 0) {for (int len = 0; len }


IdempotentConfig.java

package com.basal.common.idempotent.config;import com.basal.common.idempotent.aspect.IdempotentAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @Author alan.wang* @date: 2022-01-03 11:35* @desc: 将IdempotentAspect 拦截器注入到spring 容器中*/
@Configuration
public class IdempotentConfig {@Beanpublic IdempotentAspect IdempotentAspect(){IdempotentAspect idempotentAspect = new IdempotentAspect();return idempotentAspect;}
}

IdempotentException.java

package com.basal.common.idempotent.exception;/*** @Author alan.wang* @date: 2022-01-04 15:26* @desc: Idempotent 重复提交异常*/
public class IdempotentException extends RuntimeException {public IdempotentException() {super();}public IdempotentException(String message) {super(message);}public IdempotentException(String message, Throwable cause) {super(message, cause);}public IdempotentException(Throwable cause) {super(cause);}protected IdempotentException(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}}


spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.basal.common.idempotent.config.IdempotentConfig


推荐阅读
  • Java如何导入和导出Excel文件的方法和步骤详解
    本文详细介绍了在SpringBoot中使用Java导入和导出Excel文件的方法和步骤,包括添加操作Excel的依赖、自定义注解等。文章还提供了示例代码,并将代码上传至GitHub供访问。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • React项目中运用React技巧解决实际问题的总结
    本文总结了在React项目中如何运用React技巧解决一些实际问题,包括取消请求和页面卸载的关联,利用useEffect和AbortController等技术实现请求的取消。文章中的代码是简化后的例子,但思想是相通的。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • 本文介绍了在iOS开发中使用UITextField实现字符限制的方法,包括利用代理方法和使用BNTextField-Limit库的实现策略。通过这些方法,开发者可以方便地限制UITextField的字符个数和输入规则。 ... [详细]
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
  • Todayatworksomeonetriedtoconvincemethat:今天在工作中有人试图说服我:{$obj->getTableInfo()}isfine ... [详细]
  • wpf+mvvm代码组织结构及实现方式
    本文介绍了wpf+mvvm代码组织结构的由来和实现方式。作者回顾了自己大学时期接触wpf开发和mvvm模式的经历,认为mvvm模式使得开发更加专注于业务且高效。与此同时,作者指出mvvm模式相较于mvc模式的优势。文章还提到了当没有mvvm时处理数据和UI交互的例子,以及前后端分离和组件化的概念。作者希望能够只关注原始数据结构,将数据交给UI自行改变,从而解放劳动力,避免加班。 ... [详细]
author-avatar
121飒飒
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有