热门标签 | 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


推荐阅读
  • 本文详细介绍了在 CentOS 7 系统中配置 fstab 文件以实现开机自动挂载 NFS 共享目录的方法,并解决了常见的配置失败问题。 ... [详细]
  • HTTP(HyperTextTransferProtocol)是超文本传输协议的缩写,它用于传送www方式的数据。HTTP协议采用了请求响应模型。客服端向服务器发送一 ... [详细]
  • 本文详细介绍了如何在 Linux 系统上安装 JDK 1.8、MySQL 和 Redis,并提供了相应的环境配置和验证步骤。 ... [详细]
  • 秒建一个后台管理系统?用这5个开源免费的Java项目就够了
    秒建一个后台管理系统?用这5个开源免费的Java项目就够了 ... [详细]
  • 如何将TS文件转换为M3U8直播流:HLS与M3U8格式详解
    在视频传输领域,MP4虽然常见,但在直播场景中直接使用MP4格式存在诸多问题。例如,MP4文件的头部信息(如ftyp、moov)较大,导致初始加载时间较长,影响用户体验。相比之下,HLS(HTTP Live Streaming)协议及其M3U8格式更具优势。HLS通过将视频切分成多个小片段,并生成一个M3U8播放列表文件,实现低延迟和高稳定性。本文详细介绍了如何将TS文件转换为M3U8直播流,包括技术原理和具体操作步骤,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 本文将介绍如何在混合开发(Hybrid)应用中实现Native与HTML5的交互,包括基本概念、学习目标以及具体的实现步骤。 ... [详细]
  • 我有一个从C项目编译的.o文件,该文件引用了名为init_static_pool ... [详细]
  • 本文详细介绍了如何在PHP中记录和管理行为日志,包括ThinkPHP框架中的日志记录方法、日志的用途、实现原理以及相关配置。 ... [详细]
  • 用阿里云的免费 SSL 证书让网站从 HTTP 换成 HTTPS
    HTTP协议是不加密传输数据的,也就是用户跟你的网站之间传递数据有可能在途中被截获,破解传递的真实内容,所以使用不加密的HTTP的网站是不 ... [详细]
  • 本文介绍如何在 Android 中自定义加载对话框 CustomProgressDialog,包括自定义 View 类和 XML 布局文件的详细步骤。 ... [详细]
  • 如何在Java中使用DButils类
    这期内容当中小编将会给大家带来有关如何在Java中使用DButils类,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。D ... [详细]
  • 本文介绍了如何利用 `matplotlib` 库中的 `FuncAnimation` 类将 Python 中的动态图像保存为视频文件。通过详细解释 `FuncAnimation` 类的参数和方法,文章提供了多种实用技巧,帮助用户高效地生成高质量的动态图像视频。此外,还探讨了不同视频编码器的选择及其对输出文件质量的影响,为读者提供了全面的技术指导。 ... [详细]
  • 在JavaWeb开发中,文件上传是一个常见的需求。无论是通过表单还是其他方式上传文件,都必须使用POST请求。前端部分通常采用HTML表单来实现文件选择和提交功能。后端则利用Apache Commons FileUpload库来处理上传的文件,该库提供了强大的文件解析和存储能力,能够高效地处理各种文件类型。此外,为了提高系统的安全性和稳定性,还需要对上传文件的大小、格式等进行严格的校验和限制。 ... [详细]
  • XAMPP 遇到 404 错误:无法找到请求的对象
    在使用 XAMPP 时遇到 404 错误,表示请求的对象未找到。通过详细分析发现,该问题可能由以下原因引起:1. `httpd-vhosts.conf` 文件中的配置路径错误;2. `public` 目录下缺少 `.htaccess` 文件。建议检查并修正这些配置,以确保服务器能够正确识别和访问所需的文件路径。 ... [详细]
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
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社区 版权所有