当前位置: 首页 > news >正文

开源宝藏: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

相关文章:

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

首先&#xff0c;说下重复提交问题&#xff0c;基本上解决方案&#xff0c;核心都是根据URL、参数、token等&#xff0c;有一个唯一值检验是否重复提交。 而下面这个是根据用户id&#xff0c;唯一值进行判定&#xff0c;使用两种缓存方式&#xff0c;redis和caffeine&#xff…...

使用 npm 安装 Electron 作为开发依赖

好的&#xff0c;下面是一个使用 npm pack 和 npm install 命令来打包和安装离线版本的 npm 包的具体示例。我们将以 electron 为例&#xff0c;演示如何在有网络连接的机器上打包 electron&#xff0c;然后在没有网络连接的机器上安装它。 步骤 1: 在有网络连接的机器上打包 …...

JavaWeb之综合案例

前言 这一节讲一个案例 1. 环境搭建 然后就是把这些数据全部用到sql语句中执行 2.查询所有-后台&前台 我们先写后台代码 2.1 后台 2.2 Dao BrandMapper&#xff1a; 注意因为数据库里面的名称是下划线分割的&#xff0c;我们类里面是驼峰的&#xff0c;所以要映射 …...

MySQL 报错:1137 - Can‘t reopen table

MySQL 报错&#xff1a;1137 - Can’t reopen table 1. 问题 对临时表查询&#xff1a; select a.ts_code,a.tsnum,b.tsnum from (select t.ts_code ,count(*) tsnum from tmp_table t group by t.ts_code having count(*) > 20 and count(*)< 50 ) a ,(select t.ts_…...

Claude3.5-Sonnet和GPT-4o怎么选(附使用链接)

随着人工智能模型的不断进化&#xff0c;传统的评估标准已经逐渐变得陈旧和不再适用。以经典的“喝水测试”为例&#xff0c;过去广泛应用于检测模型能力&#xff0c;但现如今即便是国内的一些先进模型&#xff0c;也能够轻松答对这些简单的问题。因此&#xff0c;我们亟需引入…...

使用itextpdf进行pdf模版填充中文文本时部分字不显示问题

在网上找了很多种办法 都解决不了; 最后发现是文本域字体设置出了问题; 在这不展示其他的代码 只展示重要代码; 1 引入扩展包 <dependency><groupId>com.itextpdf</groupId><artifactId>itext-asian</artifactId><version>5.2.0</v…...

java-贪心算法

1. 霍夫曼编码&#xff08;Huffman Coding&#xff09; 描述&#xff1a; 霍夫曼编码是一种使用变长编码表对数据进行编码的算法&#xff0c;由David A. Huffman在1952年发明。它是一种贪心算法&#xff0c;用于数据压缩。霍夫曼编码通过构建一个二叉树&#xff08;霍夫曼树&a…...

OpenCV和Qt坐标系不一致问题

“ OpenCV和QT坐标系导致绘图精度下降问题。” OpenCV和Qt常用的坐标系都是笛卡尔坐标系&#xff0c;但是细微处有些不同。 01 — OpenCV坐标系 OpenCV是图像处理库&#xff0c;是以图像像素为一个坐标位置&#xff0c;即一个像素对应一个坐标&#xff0c;所以其坐标系也叫图像…...

前端VUE项目启动方式

将VUE项目的前端项目运行起来&#xff0c;整个过程非常简单&#xff0c;预计5分钟就可以完成&#xff0c;取决于大家的网速。 项目运行先安装Node.js Windows 安装 Node.js 指南&#xff1a;http://www.iocoder.cn/NodeJS/windows-install(opens new window) Mac 安装 Node.js…...

Python小白学习教程从入门到入坑------习题课5(基础巩固)

目录 实战题 1、“千年虫”是什么虫? 2、模拟京东购物流程 3、模拟12306火车票订票流程 4、模拟手机通讯录 实战题 1、“千年虫”是什么虫? 要求:已知一个列表中存储的是员工的出生年份 [88,89,90,98,00,99] 由于时间比较久&#xff0c;出生的年份均为2位整数&#xf…...

飞凌嵌入式T113-i开发板RISC-V核的实时应用方案

随着市场对嵌入式设备的功能需求越来越高&#xff0c;集成了嵌入式处理器和实时处理器的主控方案日益增多&#xff0c;以便更好地平衡性能与效率——实时核负责高实时性任务&#xff0c;A核处理复杂任务&#xff0c;两核间需实时交换数据。然而在数据传输方面&#xff0c;传统串…...

基于Java后台实现百度、高德和WGS84坐标的转换实战

目录 前言 一、需求的缘由 1、百度坐标拾取 2、高德坐标拾取 3、不同地图的坐标展示 二、后端坐标偏移转换处理 1、相关类库介绍 2、coordtransorm类图介绍 3、后台实际转换 三、总结 前言 在当今数字化时代&#xff0c;地理位置信息的精确性和实时性对于各种应用至…...

SQL,力扣题目1635,Hopper 公司查询 I

一、力扣链接 LeetCode_1635 二、题目描述 表: Drivers ---------------------- | Column Name | Type | ---------------------- | driver_id | int | | join_date | date | ---------------------- driver_id 是该表的主键(具有唯一值的列)。 该表的每一行…...

Android 分区相关介绍

目录 一、MTK平台 1、MTK平台分区表配置 2、MTK平台刷机配置表 3、MTK平台分区表配置不生效 4、Super分区的研究 1&#xff09;Super partition layout 2&#xff09;Block device table 二、高通平台 三、展锐平台 四、相关案例 1、Super分区不够导致编译报错 经验…...

JMeter监听器与压测监控之 InfluxDB

1. 简介 在本文中&#xff0c;我们将介绍如何在 Kali Linux 上通过 Docker 安装 InfluxDB&#xff0c;并使用 JMeter 对其进行性能监控。InfluxDB 是一个高性能的时序数据库&#xff0c;而 JMeter 是一个开源的性能测试工具&#xff0c;可以用于对各种服务进行负载测试和性能监…...

信息安全管理与评估赛项(网络安全)--应急响应专项训练

web1 题目来源&#xff1a;https://mp.weixin.qq.com/s/89IS3jPePjBHFKPXnGmKfA 题目 1.攻击者的shell密码2.攻击者的IP地址3.攻击者的隐藏账户名称4.攻击者挖矿程序的矿池域名(仅域名)5.有实力的可以尝试着修复漏洞靶机 用户:administrator密码:Zgsfadmin.com题解 攻击者…...

ElasticSearch学习篇18_《检索技术核心20讲》LevelDB设计思想

目录 一些常见的设计思想以及基于LSM树的LevelDB是如何利用这些设计思想优化存储、检索效率的。 几种常见的设计思想 索引和数据分离减少磁盘IO读写分离分层思想 LevelDB的设计思想 读写分离设计分层设计与延迟合并LRU缓存加速检索 几种常见设计思想 索引与数据分离 索引…...

使用 FFmpeg 提取音频的详细指南

FFmpeg 是一个开源的多媒体处理工具&#xff0c;支持视频、音频的编码、解码、转换等多种功能。通过 FFmpeg&#xff0c;提取视频中的音频并保存为各种格式非常简单和高效。这在音视频剪辑、媒体处理、转码等场景中具有广泛的应用。 本文将详细讲解如何使用 FFmpeg 提取音频&a…...

中国省级新质生产力发展指数数据(任宇新版本)2010-2023年

一、测算方式&#xff1a;参考C刊《财经理论与实践》任宇新&#xff08;2024&#xff09;老师的研究&#xff0c;新质生产力以劳动者劳动资料劳动对象及其优化组合的质变为 基本内涵&#xff0c;借 鉴 王 珏 和 王 荣 基 的 做 法构建新质生产力发展水平评价指标体系如下所示&a…...

C++设计模式:建造者模式(Builder) 房屋建造案例

什么是建造者模式&#xff1f; 建造者模式是一种创建型设计模式&#xff0c;它用于一步步地构建一个复杂对象&#xff0c;同时将对象的构建过程与它的表示分离开。简单来说&#xff1a; 它将复杂对象的“建造步骤”分成多部分&#xff0c;让我们可以灵活地控制这些步骤。通过…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

Android15默认授权浮窗权限

我们经常有那种需求&#xff0c;客户需要定制的apk集成在ROM中&#xff0c;并且默认授予其【显示在其他应用的上层】权限&#xff0c;也就是我们常说的浮窗权限&#xff0c;那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

基于matlab策略迭代和值迭代法的动态规划

经典的基于策略迭代和值迭代法的动态规划matlab代码&#xff0c;实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行

项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战&#xff0c;克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...

OD 算法题 B卷【正整数到Excel编号之间的转换】

文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的&#xff1a;a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...

32单片机——基本定时器

STM32F103有众多的定时器&#xff0c;其中包括2个基本定时器&#xff08;TIM6和TIM7&#xff09;、4个通用定时器&#xff08;TIM2~TIM5&#xff09;、2个高级控制定时器&#xff08;TIM1和TIM8&#xff09;&#xff0c;这些定时器彼此完全独立&#xff0c;不共享任何资源 1、定…...