使用切面实现前端重复提交(防抖)

news2024/11/24 18:31:37

使用切面实现前端重复提交(防抖)

  • 代码结构
  • 定义注解
  • 请求锁切面处理器
  • 入参对象
  • 使用注解

代码结构

在这里插入图片描述

原理:
1、前端提交保存操作;
2、后端通过注解指定重复提交的关键字段进行识别,可以有多个;
3、拼接关键字段,缓存到redis中,设置到期时间(默认3秒);
4、命中缓存则进行防抖处理,否则进行正常业务。

定义注解

/**
 * @description 请求防抖锁,用于防止前端重复提交导致的错误
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RequestLock {
	/**redis 锁前缀 */
	String prefix() default "";
	/**redis 锁过期时间, 默认3秒 */
	int expire() default 3;
	/** redis 锁过期时间单位,默认单位为秒 */
	TimeUnit timeUnit() default TimeUnit.SECONDS;
	/** redis key分隔符, 分隔符 */
	String delimiter() default ":";
	/** 防抖关键标识,使用spring el 表达式<br>
	 * 例子:<br>
	 * 第一个参数(对象)中的一个属性(电话号码) : params[0].phone<br>
	 * 第一个参数(Map)中的键值对(电话号码) : params[0]['phone']<br>
	 */
	String[] keys();
}

请求锁切面处理器

/**
 * @description 请求锁切面处理器
 */
@Aspect
@Configuration
public class RequestLockAspect {
	@Autowired
	StringRedisTemplate stringRedisTemplate;

	/** 切入点声明 */
	@Pointcut("execution(public * * (..)) && @annotation(zzc.learn.springboot.demo.repeatsubmit.annotation.RequestLock)")
	public void pointcut() {
		// do nothing
	}

	/** 定义切面:环绕通知  */
	@Around("pointcut()")
	public Object interceptor(ProceedingJoinPoint joinPoint) {
		MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
		Method method = methodSignature.getMethod();
		RequestLock requestLock = method.getAnnotation(RequestLock.class);
		if (StringUtils.isEmpty(requestLock.prefix())) {
			//            throw new RuntimeException("重复提交前缀不能为空");
			return "重复提交前缀不能为空";
		}
		//获取自定义key
		final String lockKey = _getLockKey(joinPoint);
		final Boolean success = stringRedisTemplate.execute(
				(RedisCallback<Boolean>) connection -> connection.set(lockKey.getBytes(), new byte[0], Expiration.from(requestLock.expire(), requestLock.timeUnit())
						, RedisStringCommands.SetOption.SET_IF_ABSENT));
		if (!success) {
			//            throw new RuntimeException("您的操作太快了,请稍后重试");
			return "您的操作太快了,请稍后重试";
		}
		try {
			return joinPoint.proceed();
		} catch (Throwable throwable) {
			//            throw new RuntimeException("系统异常");
			return "系统异常";
		}
	}

	/** 获取防抖提交对应的rediss的key */
	private String _getLockKey(ProceedingJoinPoint joinPoint) {
		//获取连接点的方法签名对象
		MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
		//Method对象
		Method method = methodSignature.getMethod();
		//获取Method对象上的注解对象
		RequestLock requestLock = method.getAnnotation(RequestLock.class);
		//获取方法参数
		List<Object> spelValueist = _getSpelValueList(requestLock, joinPoint.getArgs());

		StringBuilder sb = new StringBuilder();
		for (Object object : spelValueist) {
			sb.append(requestLock.delimiter()).append(object);
		}
		//返回指定前缀的key
		return requestLock.prefix() + sb;
	}

	/** 根据spel表达式读取对应参数的值 */
	private List<Object> _getSpelValueList(RequestLock requestLock, final Object[] args) {
		List<Object> spelValueist = new ArrayList<>();
		ExpressionParser parser = new SpelExpressionParser();
		EvaluationContext context = new StandardEvaluationContext();
		context.setVariable("params", args);
		String[] spelArr = requestLock.keys();
		for (int i = 0; i < spelArr.length; i++) {
			String expressionString = spelArr[i];
			if(StringUtils.isBlank(expressionString)) {
				continue;
			}
			Expression expression = parser.parseExpression(expressionString);
			spelValueist.add(expression.getValue(context));
		}
		return spelValueist;
	}
}

入参对象

@Data
public class User {
	private String name;
	private Integer age;
	private String phone;
}

使用注解

@Api(value = "重复提交测试", tags = {"重复提交测试"})
@RestController
@RequestMapping("/repeatsubmit/user")
public class UserController {

	@ApiOperation(value = "增加用户(不限制重复提交)", notes = "增加用户1")
	@PostMapping("/addUser1")
	public String addUser1(@RequestBody User user) {
		System.out.println("不做任何处理" + user);
		return "添加成功";
	}

	@ApiOperation(value = "增加对象类型用户(限制重复提交)", notes = "增加用户2")
	@PostMapping("/addUser2")
	@RequestLock(prefix = "addUser", keys= {"#params[0].name","#params[0].phone"})
	public String addUser2(@RequestBody User user, @RequestParam String name) {
		System.out.println("防重提交" + user);
		return "添加成功";
	}

	@ApiOperation(value = "增加Map类型用户(限制重复提交)", notes = "增加用户3")
	@PostMapping("/addUser3")
	@RequestLock(prefix = "addUser", keys = {"#params[0]['phone']"})
	public String addUser3(@RequestBody Map<String, Object> paramMap, @RequestParam String name) {
		System.out.println("防重提交" + paramMap);
		return "添加成功";
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1194713.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

科普测量开关电源输出波形的三种方法及电源波形自动化测试步骤

开关电源波形测试就是对开关电源的输出波形进行检测和分析&#xff0c;观察开关电源参数变化&#xff0c;以此来判断开关电源的性能是否符合要求。好的开关电源对于设备以及整个电路的正常运行是非常重要的&#xff0c;因此开关电源输出波形测试是开关电源测试的重要环节&#…

RK3399平台开发系列讲解(内存篇)free 命令查看内存占用情况介绍

🚀返回专栏总目录 文章目录 一、free的使用二、free的内容📢free 指令会显示内存的使用情况,包括实体内存,虚拟的交换文件内存,共享内存区段,以及系统核心使用的缓冲区等。 一、free的使用 -b  以 Byte 为单位显示内存使用情况。-k  以 KB 为单位显示内存使用情况。…

华为防火墙双机热备配置案例

思路&#xff1a; IP和路由、ospf要两台防火墙单配&#xff0c;hrp不会同步 其它zone和策略会同步&#xff0c;只在master上配就行了 FW_A主要配置&#xff1a; hrp enable hrp interface GigabitEthernet1/0/2 remote 172.16.0.2 interface GigabitEthernet1/0/0 undo shut…

海康Visionmaster-通讯管理:ModBus 通信发送非整型 数据的方法

Modbus 通信发送数据只能为 Int 类型&#xff0c;如下图所示&#xff1a; 可以发送 Int 和 Float 数据&#xff0c;如下图所示 通信设备配置如下&#xff1a; 发送事件配置如下&#xff1a; 通信管理界面显示有问题&#xff0c;显示为 Int 类型存在一定误导&#xff1b;可以…

【性能测试】服务端中间件docker常用命令解析整理(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、搜索 docker …

LeetCode(2)移除元素【数组/字符串】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 27. 移除元素 1.题目 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原…

【机试题】LazyIterator迭代器懒加载问题

将下面这个未完成的Java工具类补充完成&#xff0c;实现懒加载的功能&#xff0c;该类需要实现Iterable接口&#xff0c;能够遍历所有数据。具体要求如下&#xff1a; 工具类提供了一个ValueLoader接口&#xff0c;用于获取数据&#xff0c;其中ValueLoader的接口定义为&#x…

Guitar Pro8.2中文版简谱制作工具

在音乐的大舞台上&#xff0c;谁不想成为一位吉他弹奏大师呢&#xff1f;但在现实中&#xff0c;学吉他并非一蹴而就&#xff0c;许多小伙伴都因为吉他的上手难度而被浇灭学习的热情。然而&#xff0c;这里有一款神奇的软件&#xff0c;叫做Guitar Pro&#xff0c;它就像是一把…

SDN和NFV笔记

目录 SDN SDN的引入 SDN的概念 SDN网络部署的方式 SDN架构 OpenFlow SDN与传统网络的区别 SDN的应用 SDN的优点 NFV NFV的概念&#xff1a; NFV的架构&#xff1a; NFV相比于传统物理网元&#xff1a; NFV与SDN的关系 NFV与SDN的相似点 NFV与SDN的不同 SDN SD…

EXPLAIN详解(MySQL)

EXPLAIN概述 EXPLAIN语句提供MySQL如何执行语句的信息。EXPLAIN与SELECT, DELETE, INSERT, REPLACE和UPDATE语句一起工作。 EXPLAIN返回SELECT语句中使用的每个表的一行信息。它按照MySQL在处理语句时读取表的顺序列出了输出中的表。MySQL使用嵌套循环连接方法解析所有连接。…

2022年12月 Python(四级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 有n个按名称排序的商品,使用对分查找法搜索任何一商品,最多查找次数为5次,则n的值可能为?()(2分) A.5 B.15 C.30 D.35 答案:C 答案解析:对分查找最多查找次数m与个数之间n的…

高斯过程回归 | GPR高斯过程回归

高斯过程回归(Gaussian Process Regression, GPR)是一种强大的非参数回归方法,它通过假设数据是从一个高斯过程中生成的来预测新的数据点。 高斯过程是一种定义在连续输入空间上的随机过程,其中任何有限集合的观测值都呈多变量高斯分布。 实现GPR的Python代码import numpy …

Coding面试题之手写线程池

原理图 JDK线程池原理 实现代码 1.线程类&#xff08;PoolThread&#xff09; 这个类用于执行任务队列中的任务。 public class PoolThread extends Thread {private final Queue<Runnable> taskQueue;private boolean isStopped false;public PoolThread(Queue<…

Python---练习:求幸运数字6

案例&#xff1a; 幸运数字6&#xff08;只要是6的倍数&#xff09;&#xff1a;输入任意数字&#xff0c;如数字8&#xff0c;生成nums列表&#xff0c;元素值为1~8&#xff0c;从中选取幸运数字移动到新列表lucky&#xff0c;打印nums与lucky。 思考&#xff1a; 要求是6的…

说说 React中的setState执行机制

一、是什么 一个组件的显示形态可以由数据状态和外部参数所决定&#xff0c;而数据状态就是state 当需要修改里面的值的状态需要通过调用setState来改变&#xff0c;从而达到更新组件内部数据的作用 如下例子&#xff1a; import React, { Component } from react export d…

ARM 基础学习记录 / ARM 裸机编程

汇编程序调用 C 程序详情 在 C 程序和 ARM 汇编程序之间相互调用时必须遵守 ATPCS 规则&#xff0c;其是基于 ARM 指令集和 THUMB 指令集过程调用的规范&#xff0c;规定了调用函数如何传递参数&#xff0c;被调用函数如何获取参数&#xff0c;以何种方式传递函数返回值。 寄存…

Linux内核有什么之内存管理子系统有什么第六回 —— 小内存分配(4)

接前一篇文章&#xff1a;Linux内核有什么之内存管理子系统有什么第五回 —— 小内存分配&#xff08;3&#xff09; 本文内容参考&#xff1a; linux进程虚拟地址空间 《趣谈Linux操作系统 核心原理篇&#xff1a;第四部分 内存管理—— 刘超》 特此致谢&#xff01; 二、小…

SpringData、SparkStreaming和Flink集成Elasticsearch

本文代码链接&#xff1a;https://download.csdn.net/download/shangjg03/88522188 1 Spring Data框架集成 1.1 Spring Data框架介绍 Spring Data是一个用于简化数据库、非关系型数据库、索引库访问&#xff0c;并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快…

vue项目pdf文件的预览

1.下载 您可以在以下网址下载pdfjsLib&#xff1a;https://github.com/mozilla/pdf.js pdfjsLib是一个开源项目&#xff0c;您可以在GitHub上找到其源代码和相关资源。 2.放置文件位置 3.进入 在index.html引入 <script src"<% BASE_URL %>static/pdfjs-dist/b…

赛宁网安入选国家工业信息安全漏洞库(CICSVD)2023年度技术组成员单

近日&#xff0c;由国家工业信息安全发展研究中心、工业信息安全产业发展联盟主办的“2023工业信息安全大会”在北京成功举行。 会上&#xff0c;国家工业信息安全发展研究中心对为国家工业信息安全漏洞库&#xff08;CICSVD&#xff09;提供技术支持的单位授牌表彰。北京赛宁…