开源宝藏:Smart-Admin 重复提交防护的 AOP 切面实现详解

首先,说下重复提交问题,基本上解决方案,核心都是根据URL、参数、token等,有一个唯一值检验是否重复提交。

而下面这个是根据用户id,唯一值进行判定,使用两种缓存方式,redis和caffeine,可以通过配置修改使用那种方式。

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.12.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>3.0.5</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId></dependency>
package net.lab1024.sa.common.module.support.repeatsubmit.annoation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 标记 需要防止重复提交 的注解<br>* 单位:毫秒** @Author 1024创新实验室: 胡克* @Date 2020-11-25 20:56:58* @Wechat zhuoda1024* @Email lab1024@163.com* @Copyright 1024创新实验室 ( https://1024lab.net )*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RepeatSubmit {/*** 重复提交间隔时间/毫秒** @return*/int value() default 300;/*** 最长间隔30s*/int MAX_INTERVAL = 30000;
}
package net.lab1024.sa.common.module.support.repeatsubmit.ticket;import java.util.function.Function;/*** 凭证(用于校验重复提交的东西)** @Author 1024创新实验室: 罗伊* @Date 2020-11-25 20:56:58* @Wechat zhuoda1024* @Email lab1024@163.com* @Copyright 1024创新实验室 ( https://1024lab.net )*/
public abstract class AbstractRepeatSubmitTicket {private Function<String, String> ticketFunction;public AbstractRepeatSubmitTicket(Function<String, String> ticketFunction) {this.ticketFunction = ticketFunction;}/*** 获取凭证** @param ticketToken* @return*/public String getTicket(String ticketToken) {return this.ticketFunction.apply(ticketToken);}/*** 获取凭证 时间戳** @param ticket* @return*/public abstract Long getTicketTimestamp(String ticket);/*** 设置本次请求时间** @param ticket*/public abstract void putTicket(String ticket);/*** 移除凭证** @param ticket*/public abstract void removeTicket(String ticket);
}
import net.lab1024.sa.common.common.constant.StringConst;
import net.lab1024.sa.common.common.util.SmartRequestUtil;
import net.lab1024.sa.common.module.support.repeatsubmit.RepeatSubmitAspect;
import net.lab1024.sa.common.module.support.repeatsubmit.ticket.RepeatSubmitCaffeineTicket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** 重复提交配置** @Author 1024创新实验室: 罗伊* @Date 2021/10/9 18:47* @Wechat zhuoda1024* @Email lab1024@163.com* @Copyright 1024创新实验室 ( https://1024lab.net )*/
@Configuration
public class RepeatSubmitConfig {@Beanpublic RepeatSubmitAspect repeatSubmitAspect() {RepeatSubmitCaffeineTicket caffeineTicket = new RepeatSubmitCaffeineTicket(this::ticket);return new RepeatSubmitAspect(caffeineTicket);}/*** 获取指明某个用户的凭证** @return*/private String ticket(String servletPath) {Long userId = SmartRequestUtil.getRequestUserId();if (null == userId) {return StringConst.EMPTY;}return servletPath + "_" + userId;}
}
package net.lab1024.sa.common.module.support.repeatsubmit.ticket;import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import net.lab1024.sa.common.module.support.repeatsubmit.annoation.RepeatSubmit;import java.util.concurrent.TimeUnit;
import java.util.function.Function;/*** 凭证(内存实现)** @Author 1024创新实验室: 罗伊* @Date 2020-11-25 20:56:58* @Wechat zhuoda1024* @Email lab1024@163.com* @Copyright 1024创新实验室 ( https://1024lab.net )*/
public class RepeatSubmitCaffeineTicket extends AbstractRepeatSubmitTicket {/*** 限制缓存最大数量 超过后先放入的会自动移除* 默认缓存时间* 初始大小为:100万*/private static Cache<String, Long> cache = Caffeine.newBuilder().maximumSize(100 * 10000).expireAfterWrite(RepeatSubmit.MAX_INTERVAL, TimeUnit.MILLISECONDS).build();public RepeatSubmitCaffeineTicket(Function<String, String> ticketFunction) {super(ticketFunction);}@Overridepublic Long getTicketTimestamp(String ticket) {return cache.getIfPresent(ticket);}@Overridepublic void putTicket(String ticket) {cache.put(ticket, System.currentTimeMillis());}@Overridepublic void removeTicket(String ticket) {cache.invalidate(ticket);}
}
package net.lab1024.sa.common.module.support.repeatsubmit.ticket;import net.lab1024.sa.common.module.support.repeatsubmit.annoation.RepeatSubmit;
import org.springframework.data.redis.core.ValueOperations;import java.util.concurrent.TimeUnit;
import java.util.function.Function;/*** 凭证(redis实现)** @Author 1024创新实验室: 罗伊* @Date 2020-11-25 20:56:58* @Wechat zhuoda1024* @Email lab1024@163.com* @Copyright 1024创新实验室 ( https://1024lab.net )*/
public class RepeatSubmitRedisTicket extends AbstractRepeatSubmitTicket {private ValueOperations<String, String> redisValueOperations;public RepeatSubmitRedisTicket(ValueOperations<String, String> redisValueOperations,Function<String, String> ticketFunction) {super(ticketFunction);this.redisValueOperations = redisValueOperations;}@Overridepublic Long getTicketTimestamp(String ticket) {Long timeStamp = System.currentTimeMillis();boolean setFlag = redisValueOperations.setIfAbsent(ticket, String.valueOf(timeStamp), RepeatSubmit.MAX_INTERVAL, TimeUnit.MILLISECONDS);if (!setFlag) {timeStamp = Long.valueOf(redisValueOperations.get(ticket));}return timeStamp;}@Overridepublic void putTicket(String ticket) {redisValueOperations.getOperations().delete(ticket);this.getTicketTimestamp(ticket);}@Overridepublic void removeTicket(String ticket) {redisValueOperations.getOperations().delete(ticket);}
}
package net.lab1024.sa.common.module.support.repeatsubmit;import lombok.extern.slf4j.Slf4j;
import net.lab1024.sa.common.common.code.UserErrorCode;
import net.lab1024.sa.common.common.domain.ResponseDTO;
import net.lab1024.sa.common.module.support.repeatsubmit.annoation.RepeatSubmit;
import net.lab1024.sa.common.module.support.repeatsubmit.ticket.AbstractRepeatSubmitTicket;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import java.lang.reflect.Method;/*** 重复提交 aop切口** @Author 1024创新实验室: 胡克* @Date 2020-11-25 20:56:58* @Wechat zhuoda1024* @Email lab1024@163.com* @Copyright 1024创新实验室 ( https://1024lab.net )*/
@Aspect
@Slf4j
public class RepeatSubmitAspect {private AbstractRepeatSubmitTicket repeatSubmitTicket;/*** 获取凭证信息* rep** @param repeatSubmitTicket*/public RepeatSubmitAspect(AbstractRepeatSubmitTicket repeatSubmitTicket) {this.repeatSubmitTicket = repeatSubmitTicket;}/*** 定义切入点** @param point* @return* @throws Throwable*/@Around("@annotation(net.lab1024.sa.common.module.support.repeatsubmit.annoation.RepeatSubmit)")public Object around(ProceedingJoinPoint point) throws Throwable {ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();String ticketToken = attributes.getRequest().getServletPath();String ticket = this.repeatSubmitTicket.getTicket(ticketToken);if (StringUtils.isEmpty(ticket)) {return point.proceed();}Long timeStamp = this.repeatSubmitTicket.getTicketTimestamp(ticket);if (timeStamp != null) {Method method = ((MethodSignature) point.getSignature()).getMethod();RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);// 说明注解去掉了if (annotation != null) {return point.proceed();}int interval = Math.min(annotation.value(), RepeatSubmit.MAX_INTERVAL);if (System.currentTimeMillis() < timeStamp + interval) {// 提交频繁return ResponseDTO.error(UserErrorCode.REPEAT_SUBMIT);}}Object obj = null;try {// 先给 ticket 设置在执行中this.repeatSubmitTicket.putTicket(ticket);obj = point.proceed();} catch (Throwable throwable) {log.error("", throwable);throw throwable;} finally {this.repeatSubmitTicket.removeTicket(ticket);}return obj;}}

参考链接:https://github.com/1024-lab/smart-admin

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

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

相关文章

Ubuntu下手动设置Nvidia显卡风扇转速

在Ubuntu下&#xff0c;您可以使用 NVIDIA显卡驱动程序提供的工具手动调整风扇转速。以下是详细步骤&#xff1a; 1. 确保已安装NVIDIA显卡驱动 确保系统已经安装了正确的NVIDIA驱动&#xff1a; nvidia-smi如果没有输出驱动信息&#xff0c;请先安装驱动&#xff1a; sudo…

【python】Python 虚拟环境的常用命令

这是一组用于设置和使用 Python 虚拟环境的常用命令。以下是逐步解析它们的含义和作用&#xff1a; 1. 创建虚拟环境 python -m venv myvenv含义&#xff1a;使用 Python 自带的 venv 模块创建一个虚拟环境&#xff0c;名称为 myvenv。作用&#xff1a; 虚拟环境是一个独立的 …

Python 爬虫从入门到(不)入狱学习笔记

爬虫的流程&#xff1a;从入门到入狱 1 获取网页内容1.1 发送 HTTP 请求1.2 Python 的 Requests 库1.2 实战&#xff1a;豆瓣电影 scrape_douban.py 2 解析网页内容2.1 HTML 网页结构2.2 Python 的 Beautiful Soup 库 3 存储或分析数据&#xff08;略&#xff09; 一般爬虫的基…

微信小程序组件详解:text 和 rich-text 组件的基本用法

微信小程序组件详解:text 和 rich-text 组件的基本用法 引言 在微信小程序的开发中,文本展示是用户界面设计中不可或缺的一部分。无论是简单的文本信息,还是复杂的富文本内容,text 和 rich-text 组件都能够帮助我们实现这些需求。本文将详细介绍这两个组件的基本用法,包…

深入探讨异步 API 的设计与实现

一、API 模式简介&#xff1a;同步与异步的对比 API 是客户端和服务器之间通信的桥梁。大多数 API 采用同步模式&#xff0c;执行的流程如下&#xff1a; 客户端发送请求。服务器处理请求。服务器返回响应。 同步模式对快速操作非常有效&#xff0c;比如数据查询或简单更新。…

黄仁勋:人形机器人在内,仅有三种机器人有望实现大规模生产

11月23日&#xff0c;芯片巨头、AI时代“卖铲人”和最大受益者、全球市值最高【英伟达】创始人兼CEO黄仁勋在香港科技大学被授予工程学荣誉博士学位&#xff1b;并与香港科技大学校董会主席沈向洋展开深刻对话&#xff0c;涉及人工智能&#xff08;AI&#xff09;、计算力、领导…

【Linux学习】【Ubuntu入门】2-3 make工具和makefile引入

1.使用命令新建三个.c文件vi main.c&#xff0c;vi input.c&#xff0c;vi caclcu.c&#xff0c;两个.h文件vi input.h&#xff0c;vi caclcu.h 2.vi Makefile&#xff1a;新建Makefile文件&#xff0c;输入一下内容 注意&#xff1a;命令列表中每条命令前用TAB键&#xff0c;不…

wsl2的Ubuntu18.04安装ros和anaconda

参考&#xff1a;超详细 WSL2 安装 ros 和 anaconda_wsl2安装anaconda-CSDN博客 一.安装ros 1. 更换系统源 输入 wget http://fishros.com/install -O fishros && . fishros 和上面的链接一样&#xff0c;依次输入5-2-1 2. 安装ros 输入 wget http://fishros.c…

1-golang_org_x_crypto_bcrypt测试 --go开源库测试

1.实例测试 package mainimport ("fmt""golang.org/x/crypto/bcrypt" )func main() {password : []byte("mysecretpassword")hashedPassword, err : bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)if err ! nil {fmt.Println(err)…

【FPGA】Verilog:利用 4 个串行输入- 串行输出的 D 触发器实现 Shift_register

0x00 什么是寄存器 寄存器(Register)是顺序逻辑电路中使用的基本组成部分之一。寄存器用于在数字系统中存储和处理数据。寄存器通常由位(bit)构成,每个位可以存储一个0或1的值。通过寄存器,可以设计出计数器、加法器等各种数据处理电路。 0x01 寄存器的种类 基于 D 触发…

CentOS 7安装SSHFS 实现远程主机目录 挂载为本地目录

安装sshfs 官方下载地址 https://github.com/libfuse/sshfs/releases 首先&#xff0c;我们需要安装sshfs软件。sshfs是一个基于SSH文件传输协议的文件系统客户端&#xff0c;它的官方网页是&#xff1a;http://fuse.sourceforge.net/sshfs.html 。在CentOS下&#xff0c;我们…

MySQL:IF()函数根据指定条件返回不同的值

语法如下&#xff1a; IF(condition, value_if_true, value_if_false) 其中&#xff0c;condition表示要判断的条件&#xff0c;如果条件成立&#xff0c;则返回value_if_true&#xff1b;如果条件不成立&#xff0c;则返回value_if_false。 案例 SELECT IF(3 > 2, True…

算法 差分修改 极简

N个气球排成一排&#xff0c;从左到右依次编号为1,2,3....N.每次给定2个整数a b(a < b),lele便为骑上他的“小飞鸽"牌电动车从气球a开始到气球b依次给每个气球涂一次颜色。但是N次以后lele已经忘记了第I个气球已经涂过几次颜色了&#xff0c;你能帮他算出每个气球被涂过…

用 Python 从零开始创建神经网络(十):优化器(Optimizers)(持续更新中...)

优化器&#xff08;Optimizers&#xff09; 引言1. 随机梯度下降/Stochastic Gradient Descent (SGD)2. 学习率&#xff08;Learning Rate&#xff09;3. 学习率衰减&#xff08;Learning Rate Decay&#xff09;4. 带动量的随机梯度下降法&#xff08;Stochastic Gradient Des…

ubity3D基础

Unity是一个流行的游戏开发引擎&#xff0c;它使用C#作为其主要的编程语言。以下是一些Unity中C#编程的基础概念&#xff1a; • Unity编辑器&#xff1a; • Unity编辑器是Unity游戏引擎的核心&#xff0c;提供了一个可视化界面&#xff0c;用于创建和管理游戏项目。 • C#脚本…

利用c语言详细介绍下栈的实现

数据结构中&#xff0c;栈是一种线性结构&#xff0c;数据元素遵循后进先出的原则。栈的一端为栈顶&#xff0c;一端为栈底或栈尾&#xff0c;数据只在栈顶端进行操作。新插入数据称为入栈或者压栈&#xff0c;删除数据叫做出栈或者退栈。 一、图文介绍 我们通过建立一个stack…

元组部分介绍

元组部分 元组的基本格式与特点 #1.元组 #基本格式&#xff1a; 元组名&#xff08;元素1&#xff0c;元素2&#xff0c;元素3&#xff09; #注意&#xff1a;所有元素包含在小括号内&#xff0c;元素与元素之间用逗号隔开&#xff0c;可以是不同的元素类型 #注意&#xff1a…

Jackson、Gson、FastJSON三款JSON利器比拼

在Java领域&#xff0c;有多种JSON工具包&#xff0c;比如Jackson、Gson、FastJSON&#xff0c;每家都各有所长&#xff0c;下面我们从性能、特性、生态、易用 性等几个方面来展开下&#xff1a; 一、Jackson 性能 Jackson是一款高性能的JSON处理库。它在序列化和反序列化操作…

使用 OpenCV 进行视频中的行人检测

在计算机视觉领域&#xff0c;行人检测是一个重要的研究方向&#xff0c;它在视频监控、自动驾驶、人机交互等领域都有着广泛的应用。本文将介绍如何使用 OpenCV 库来实现视频中的行人检测。 环境准备 首先&#xff0c;我们需要安装 OpenCV 库。可以通过以下命令来安装&#…

pytest日志总结

pytest日志分为两类&#xff1a; 一、终端&#xff08;控制台&#xff09;打印的日志 1、指定-s&#xff0c;脚本中print打印出的信息会显示在终端&#xff1b; 2、pytest打印的summary信息&#xff0c;这部分是pytest 的默认输出&#xff08;例如测试结果PASSED, FAILED, S…