开源宝藏: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.rhkb.cn/news/477243.html

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

相关文章

I.MX6U 裸机开发18.GPT定时器实现高精度延时

I.MX6U 裸机开发18.GPT定时器实现高精度延时 一、GPT定时器简介1. GPT 功能2. 时钟源3. 框图4. 运行模式&#xff08;1&#xff09;Restart mode&#xff08;2&#xff09;Free-Run Mode 5. 中断类型&#xff08;1&#xff09;溢出中断 Rollover Interrupt&#xff08;2&#x…

key-value存储实现

文章目录 一、项目简介二、项目流程图三、网络3.1、epoll实现3.2、io_uring实现 四、协议五、存储5.1、array实现5.2、rbtree实现5.3、hash实现 六、测试 一、项目简介 key-value存储其实是一个小型的redis&#xff0c;用户在客户端输入存储相关的指令发送给服务器端&#xff…

大公司如何实现打印机共享的?如何对打印机进行管控或者工号登录后进行打印?异地打印机共享的如何实现可以帮助用户在不同地理位置使用同一台打印机完成打印任务?

大公司如何实现打印机共享的&#xff1f;如何对打印机进行管控或者工号登录后进行打印&#xff1f;异地打印机共享的如何实现可以帮助用户在不同地理位置使用同一台打印机完成打印任务&#xff1f; 如果在局域网内&#xff0c;可以不需要进行二次开发&#xff0c;通过对打印机进…

微软发布Win11 24H2系统11月可选更新KB5046740!

系统之家11月22日报道&#xff0c;微软针对Win11 24H2系统推出2024年11月最新可选更新补丁KB5046740&#xff0c;更新后系统版本后升至26100.2454&#xff0c;此次更新后修复当应用程序以PDF和XLSX格式导出图表对象时停止响应、无法使用API查找旋转信息等问题。以下小编将给大家…

探索 RocketMQ:企业级消息中间件的选择与应用

一、关于RocketMQ RocketMQ 是一个高性能、高可靠、可扩展的分布式消息中间件&#xff0c;它是由阿里巴巴开发并贡献给 Apache 软件基金会的一个开源项目。RocketMQ 主要用于处理大规模、高吞吐量、低延迟的消息传递&#xff0c;它是一个轻量级的、功能强大的消息队列系统&…

李宏毅机器学习课程知识点摘要(6-13集)

pytorch简单的语法和结构 dataset就是数据集&#xff0c;dataloader就是分装好一堆一堆的 他们都是torch.utils.data里面常用的函数&#xff0c;已经封装好了 下面的步骤是把数据集读进来 这里是读进来之后&#xff0c;进行处理 声音信号&#xff0c;黑白照片&#xff0c;红…

Wekan看板安装部署与使用介绍

Wekan看板安装部署与使用介绍 1. Wekan简介 ​ Wekan 是一个开源的看板式项目管理工具&#xff0c;它的配置相对简单&#xff0c;因为大多数功能都是开箱即用的。它允许用户以卡片的形式组织和跟踪任务&#xff0c;非常适合敏捷开发和日常任务管理。Wekan 的核心功能包括看板…

【Mysql】开窗聚合函数----SUM,AVG, MIN,MAX

1、概念 在窗口中&#xff0c;每条记录动态地应用聚合函数&#xff08;如&#xff1a;SUM(),AVG(),MAX(),MIN(),COUNT(),&#xff09;可以动态计算在指定的窗口内的各种聚合函数值。 2、操作 以下操作将基于employee表进行操作。 sum() 进行sum的时候&#xff0c;没有order …

EWA Volume Splatting

摘要 本文提出了一种基于椭圆高斯核的直接体绘制新框架&#xff0c;使用了一种投影方法&#xff08;splatting approach&#xff09;。为避免混叠伪影&#xff08;aliasing artifacts&#xff09;&#xff0c;我们引入了一种重采样滤波器的概念&#xff0c;该滤波器结合了重建核…

Vue实训---0-完成Vue开发环境的搭建

1.在官网下载和安装VS Code编辑器 完成中文语言扩展&#xff08;chinese&#xff09;&#xff0c;安装成功后&#xff0c;需要重新启动VS Code编辑器&#xff0c;中文语言扩展才可以生效。 安装Vue-Official扩展&#xff0c;步骤与安装中文语言扩展相同&#xff08;专门用于为“…

C# 超链接控件LinkLabel无法触发Alt快捷键

在C#中&#xff0c;为控件添加快捷键的方式有两种&#xff0c;其中一种就是Windows中较为常见的Alt快捷键&#xff0c;比如运行对话框&#xff0c;记事本菜单等。只需要按下 Alt 框号中带下划线的字母即可触发该控件的点击操作。如图所示 在C#开发中&#xff0c;实现类似的操作…

赛氪媒体支持“2024科普中国青年之星创作交流活动”医学专场落幕

2024年11月15日下午&#xff0c;由中国科普作家协会、科普中国发展服务中心主办&#xff0c;什刹海文化展示中心承办&#xff0c;并携手国内产学研一体融合领域的领军者——赛氪网共同支持的“2024科普中国青年之星创作交流活动”医学科普专场&#xff0c;在什刹海文化展示中心…

《现代制造技术与装备》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答 问&#xff1a;《现代制造技术与装备》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的第二批认定学术期刊。 问&#xff1a;《现代制造技术与装备》级别&#xff1f; 答&#xff1a;省级。主管单位&#xff1a;齐鲁工业大学&#xff0…

(十一)Python字符串常用操作

一、访问字符串值 Python访问子字符串变量&#xff0c;可以使用方括号来截取字符串。与列表的索引一样&#xff0c;字符串索引从0开始。 hh"LaoTie 666" hh[2] mm"床前明月光" mm[3] 字符串的索引值可以为负值。若索引值为负数&#xff0c;则表示由字符…

数据结构(初阶6)---二叉树(遍历——递归的艺术)(详解)

二叉树的遍历与练习 一.二叉树的基本遍历形式1.前序遍历(深度优先遍历)2.中序遍历(深度优先遍历)3.后序遍历(深度优先遍历)4.层序遍历&#xff01;&#xff01;(广度优先遍历) 二.二叉树的leetcode小练习1.判断平衡二叉树1&#xff09;正常解法2&#xff09;优化解法 2.对称二叉…

20.100ASK_T113-PRO 开发板开机自动QT程序简单的方法一

本文详细介绍了在嵌入式系统中实现程序开机自启动的多种方法&#xff0c;包括通过修改/etc/profile、/etc/rc.local文件&#xff0c;以及在/etc/init.d目录下创建启动脚本等方式。文章还解释了不同配置文件的作用及它们之间的区别。 开机自动启动QT应用程序 用户模式下的启动 …

Android蓝牙架构,源文件目录/编译方式学习

Android 版本 发布时间 代号&#xff08;Codename&#xff09; Android 1.0 2008年9月23日 无 Android 1.1 2009年2月9日 Petit Four Android 1.5 2009年4月27日 Cupcake Android 1.6 2009年9月15日 Donut Android 2.0 2009年10月26日 Eclair Android 2.1 2…

LeetCode 145.二叉树的后序遍历

题目&#xff1a;给你一棵二叉树的根节点 root &#xff0c;返回其节点值的 后序遍历 。 思路&#xff1a;左 右 根 代码&#xff1a; /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* Tre…

GitLab|数据迁移

注意&#xff1a;新服务器GitLab版本需和旧版本一致 在旧服务器执行命令进行数据备份 gitlab-rake gitlab:backup:create 备份数据存储在 /var/opt/gitlab/backups/ 将备份数据传输到新服务器的/var/opt/gitlab/backups/下&#xff0c;并修改文件权限&#xff08;下载前和上传…

实验四:构建园区网(OSPF 动态路由)

目录 一、实验简介 二、实验目的 三、实验需求 四、实验拓扑 五、实验步骤 1、在 eNSP 中部署网络 2、设计全网 IP 地址 3、配置二层交换机 4、配置路由交换机并测试通信 5、配置路由接口地址 6、配置 OSPF 动态路由&#xff0c;实现全网互通 一、实验简介 使用路由…