java 基于注解的防重复提交

由于前端未作防抖校验或者接口调用者出现问题导致调用多次情况时有发生,像查询这种请求即使多次请求也不会造成什么影响,但是涉及增删改可就影响大了。

1、首先定义一个自定义注解

/*** 自定义注解防止表单重复提交* * @author fjh**/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit
{/*** 间隔时间(ms),小于此时间视为重复提交*/public int interval() default 5000;/*** 提示消息*/public String message() default "不允许重复提交,请稍候再试";
}

2、创建请求拦截器


/*** 防止重复提交拦截器** @author fjh*/
@Component
public abstract class RepeatSubmitInterceptor implements HandlerInterceptor
{@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{if (handler instanceof HandlerMethod){HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);if (annotation != null){if (this.isRepeatSubmit(request, annotation)){AjaxResult ajaxResult = AjaxResult.error(annotation.message());ServletUtils.renderString(response, JSON.toJSONString(ajaxResult));return false;}}return true;}else{return true;}}/*** 验证是否重复提交由子类实现具体的防重复提交的规则** @param request* @return* @throws Exception*/public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);
}

3、验证是否重复提交由子类实现具体的防重复提交的规则

/*** 判断请求url和数据是否和上一次相同,* 如果和上次相同,则是重复提交表单。 有效时间为10秒内。* * @author fjh*/
@Component
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
{public final String REPEAT_PARAMS = "repeatParams";public final String REPEAT_TIME = "repeatTime";// 令牌自定义标识@Value("${token.header}")private String header;@Autowiredprivate RedisCache redisCache;@SuppressWarnings("unchecked")@Overridepublic boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation){String nowParams = "";if (request instanceof RepeatedlyRequestWrapper){RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;nowParams = HttpHelper.getBodyString(repeatedlyRequest);}// body参数为空,获取Parameter的数据if (StringUtils.isEmpty(nowParams)){nowParams = JSON.toJSONString(request.getParameterMap());}Map<String, Object> nowDataMap = new HashMap<String, Object>();nowDataMap.put(REPEAT_PARAMS, nowParams);nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());// 请求地址(作为存放cache的key值)String url = request.getRequestURI();// 唯一值(没有消息头则使用请求地址)String submitKey = StringUtils.trimToEmpty(request.getHeader(header));// 唯一标识(指定key + url + 消息头)String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + url + submitKey;Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);if (sessionObj != null){Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;if (sessionMap.containsKey(url)){Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())){return true;}}}Map<String, Object> cacheMap = new HashMap<String, Object>();cacheMap.put(url, nowDataMap);redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS);return false;}/*** 判断参数是否相同*/private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap){String nowParams = (String) nowMap.get(REPEAT_PARAMS);String preParams = (String) preMap.get(REPEAT_PARAMS);return nowParams.equals(preParams);}/*** 判断两次间隔时间*/private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, int interval){long time1 = (Long) nowMap.get(REPEAT_TIME);long time2 = (Long) preMap.get(REPEAT_TIME);if ((time1 - time2) < interval){return true;}return false;}
}

4、公共类

/*** 操作消息提醒* * @author fjh*/
public class AjaxResult extends HashMap<String, Object>
{private static final long serialVersionUID = 1L;/** 状态码 */public static final String CODE_TAG = "code";/** 返回内容 */public static final String MSG_TAG = "msg";/** 数据对象 */public static final String DATA_TAG = "data";/*** 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。*/public AjaxResult(){}/*** 初始化一个新创建的 AjaxResult 对象* * @param code 状态码* @param msg 返回内容*/public AjaxResult(int code, String msg){super.put(CODE_TAG, code);super.put(MSG_TAG, msg);}/*** 初始化一个新创建的 AjaxResult 对象* * @param code 状态码* @param msg 返回内容* @param data 数据对象*/public AjaxResult(int code, String msg, Object data){super.put(CODE_TAG, code);super.put(MSG_TAG, msg);if (StringUtils.isNotNull(data)){super.put(DATA_TAG, data);}}/*** 返回成功消息* * @return 成功消息*/public static AjaxResult success(){return AjaxResult.success("操作成功");}/*** 返回成功数据* * @return 成功消息*/public static AjaxResult success(Object data){return AjaxResult.success("操作成功", data);}/*** 返回成功消息* * @param msg 返回内容* @return 成功消息*/public static AjaxResult success(String msg){return AjaxResult.success(msg, null);}/*** 返回成功消息* * @param msg 返回内容* @param data 数据对象* @return 成功消息*/public static AjaxResult success(String msg, Object data){return new AjaxResult(HttpStatus.SUCCESS, msg, data);}/*** 返回错误消息* * @return*/public static AjaxResult error(){return AjaxResult.error("操作失败");}/*** 返回错误消息* * @param msg 返回内容* @return 警告消息*/public static AjaxResult error(String msg){return AjaxResult.error(msg, null);}/*** 返回错误消息* * @param msg 返回内容* @param data 数据对象* @return 警告消息*/public static AjaxResult error(String msg, Object data){return new AjaxResult(HttpStatus.ERROR, msg, data);}/*** 返回错误消息* * @param code 状态码* @param msg 返回内容* @return 警告消息*/public static AjaxResult error(int code, String msg){return new AjaxResult(code, msg, null);}/*** 方便链式调用** @param key 键* @param value 值* @return 数据对象*/@Overridepublic AjaxResult put(String key, Object value){super.put(key, value);return this;}
}

5、工具类

/*** 客户端工具类* * @author fjh*/
public class ServletUtils
{/*** 获取String参数*/public static String getParameter(String name){return getRequest().getParameter(name);}/*** 获取String参数*/public static String getParameter(String name, String defaultValue){return Convert.toStr(getRequest().getParameter(name), defaultValue);}/*** 获取Integer参数*/public static Integer getParameterToInt(String name){return Convert.toInt(getRequest().getParameter(name));}/*** 获取Integer参数*/public static Integer getParameterToInt(String name, Integer defaultValue){return Convert.toInt(getRequest().getParameter(name), defaultValue);}/*** 获取Boolean参数*/public static Boolean getParameterToBool(String name){return Convert.toBool(getRequest().getParameter(name));}/*** 获取Boolean参数*/public static Boolean getParameterToBool(String name, Boolean defaultValue){return Convert.toBool(getRequest().getParameter(name), defaultValue);}/*** 获取request*/public static HttpServletRequest getRequest(){return getRequestAttributes().getRequest();}/*** 获取response*/public static HttpServletResponse getResponse(){return getRequestAttributes().getResponse();}/*** 获取session*/public static HttpSession getSession(){return getRequest().getSession();}public static ServletRequestAttributes getRequestAttributes(){RequestAttributes attributes = RequestContextHolder.getRequestAttributes();return (ServletRequestAttributes) attributes;}/*** 将字符串渲染到客户端* * @param response 渲染对象* @param string 待渲染的字符串*/public static void renderString(HttpServletResponse response, String string){try{response.setStatus(200);response.setContentType("application/json");response.setCharacterEncoding("utf-8");response.getWriter().print(string);}catch (IOException e){e.printStackTrace();}}/*** 是否是Ajax异步请求* * @param request*/public static boolean isAjaxRequest(HttpServletRequest request){String accept = request.getHeader("accept");if (accept != null && accept.contains("application/json")){return true;}String xRequestedWith = request.getHeader("X-Requested-With");if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")){return true;}String uri = request.getRequestURI();if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml")){return true;}String ajax = request.getParameter("__ajax");return StringUtils.inStringIgnoreCase(ajax, "json", "xml");}
}---/*** 通用http工具封装* * @author fjh*/
public class HttpHelper
{private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class);public static String getBodyString(ServletRequest request){StringBuilder sb = new StringBuilder();BufferedReader reader = null;try (InputStream inputStream = request.getInputStream()){reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));String line = "";while ((line = reader.readLine()) != null){sb.append(line);}}catch (IOException e){LOGGER.warn("getBodyString出现问题!");}finally{if (reader != null){try{reader.close();}catch (IOException e){LOGGER.error(ExceptionUtils.getMessage(e));}}}return sb.toString();}
}

6、后赋上使用方式
在需要使用的接口上添加@RepeatSubmit 即可

 /*** 修改设备管理*/@Log(title = "更新", businessType = BusinessType.UPDATE)@PutMapping@RepeatSubmit(interval = 1000, message = "修改更新管理")public AjaxResult edit(@RequestBody BDeviceManager bDeviceManager) {return toAjax(bDeviceManagerService.updateBDeviceManager(bDeviceManager));}

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

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

相关文章

数字生命计划1.0

一. 背景 即使人们强调思维与肉体是两大构成个体的先决条件&#xff0c;但科学界始终没有放弃探索摆脱肉体的介质&#xff0c;从而让思维上载的研究。 这就是数字生命计划的初衷。所谓数字生命&#xff0c;就是将人类的意识思维抽离&#xff0c;上传至云端或智能终端&#xf…

eMMC的封装类型及特点有哪些?

eMMC&#xff08;Embedded MultiMediaCard&#xff09;是一种高度集成的嵌入式存储解决方案&#xff0c;广泛应用于智能手机、平板电脑、笔记本电脑、消费电子产品和物联网设备等领域。eMMC的封装形式对其性能、应用和设备设计有着重要的影响。目前&#xff0c;eMMC主要有两种封…

航空航天技术翻译要求

航空航天技术是一项国际化产业&#xff0c;航空技术更新迅速涉及的专业知识较为广泛。在航空领域&#xff0c;每一个术语都有着特定的含义和用法&#xff0c;稍有差池就可能导致翻译出现严重的后果&#xff0c;因此&#xff0c;航空航天翻译扮演者至关重要的角色。 专业航空航天…

【ARM Cache 与 MMU 系列文章 7.6 -- ARMv8 MMU 相关寄存器介绍】

文章目录 MMU 转换控制寄存器 TCR_ELxTCR_ELx 概览TCR_ELx 寄存器字段详解TCR 使用示例Normal MemoryCacheableShareability MMU 内存属性寄存器 MAIR_ELx寄存器结构内存属性字段使用实例 MMU 地址翻译表基址寄存器 TTBR0/1_ELxTTBR0_ELx 寄存器概述寄存器结构功能和用途编程示…

在Spark SQL中,fillna函数

目录 前言 以下是在Spark SQL中使用fillna函数的示例代码&#xff1a; 运行以上代码将输出&#xff1a; 总结 前言 在Spark SQL中&#xff0c;fillna函数用于填充DataFrame或Dataset中的缺失值&#xff08;NULL或NaN&#xff09;。它可以根据指定的列名和值来替换缺失值&…

idea Pycharm Webstorm 2024年 最新版 永久使用2099年教程 附激活码亲测可用

idea Pycharm Webstorm 2024年 最新版 永久使用2099年教程 附激活码亲测可用 链接&#xff1a; 下载 IntelliJ IDEA – 领先的 Java 和 Kotlin IDE (jetbrains.com)&#xff08;这是官网的&#xff09; 下载 安装 1.双击idea的安装包&#xff0c;点击next 选择创建桌面快捷…

深入剖析时序Prophet模型:工作原理与源码解析|得物技术

随着得物业务的快速发展&#xff0c;积累了大量的时序数据&#xff0c;这些数据对精细化运营&#xff0c;提升效率、降低成本有着重要作用。在得物的时序数据挖掘场景中&#xff0c;时序预测Prophet模型使用频繁&#xff0c;本文对Prophet的原理和源码进行深入分析&#xff0c;…

linux学习:进程通信 管道

目录 例子1 父进程向子进程发送一条消息&#xff0c;子进程读取这条消息 例子2 mkfifo 函数创建一个命名管道 例子3 mkfifo 函数创建一个命名管道处理可能出现的错误 例子4 管道文件是否已存在 例子5 除了“文件已存在”进行处理 例子6 创建一个命名管道&…

删除docker 镜像和容器

删除docker镜像&#xff0c;需要先删除容器&#xff1b; 删除docker容器&#xff0c;需要先停止容器&#xff1b; ①查询容器 docker ps -a 查看相关容器&#xff08;运行中和停止的&#xff09; ②停止容器 docker stop 容器id ③删除容器docker rm 容器id ④docker rmi…

边缘计算网关:企业数字化转型的重要支撑-天拓四方

在数字化浪潮席卷全球的今天&#xff0c;企业对于数据处理和传输的需求日益增强。然而&#xff0c;传统的数据处理模式往往依赖于中心化的数据中心&#xff0c;这种方式在处理大量数据时存在延迟高、成本高、安全性差等问题。数据量的激增和实时性要求的提高&#xff0c;使得传…

让AI给你写代码(9.3):一点改进,支持扩展本地知识库

改进目标&#xff0c;当输入提示问题后&#xff0c;能匹配到本地知识库的需求&#xff0c;然后AI按匹配到的需求给出代码并进行自动测试&#xff1b; 如果无法匹配到本地需求&#xff0c;可以直接输入生成逻辑&#xff0c;再由AI生成&#xff0c;然后支持用户把新需求插入本地库…

XML概述、格式、解析-WEB

XML概述 XML&#xff08;Extensible Markup Language&#xff09; 可扩展的标识语言 数据传输的一种格式 树形结构&#xff08;节点&#xff09; 优点 解析的更快 : 使用标签语言&#xff0c;能够很快定位 占用空间小 : 纯文本格式 可读性强 : 带有名字的标签我们都很喜欢 跨平…

MathType7.9最新免费注册机注册码激活码分享

MathType作为一个强大的数学公式编辑器&#xff0c;广泛用于教育、科研和出版领域。它支持多种文档格式和数学符号&#xff0c;是撰写科技文档不可或缺的工具。对于一些个人用户和小型机构来说&#xff0c;正版软件的高昂价格可能是一个负担。因此&#xff0c;寻找合法途径下的…

Windows 10 以上版本用久了应该重新安装还是重置呢?

今天明月工作电脑的 Windows 10 因为安装过太多软件和使用太久了的缘故造成网络总是很不稳定&#xff0c;总是出现一些莫名其妙的问题&#xff0c;实在是懒得去查找和排错了&#xff0c;就想着干脆推到重来吧&#xff0c;这样性能表现还会提升不少&#xff0c;并且可以一次性的…

向量化:机器学习中的效率加速器与数据桥梁

在机器学习领域的广袤天地中&#xff0c;向量化技术以其独特的魅力&#xff0c;为数据处理和模型训练注入了强大的动力。本文将深入探讨向量化在机器学习领域中的体现&#xff0c;剖析其如何助力模型实现高效的数据处理和精确的结果预测&#xff0c;并通过丰富的案例和详尽的数…

容器中运行ping提示bash: ping: command not found【笔记】

容器中运行ping提示bash: ping: command not found 原因是容器中没有安装ping命令 在容器中安装ping命令&#xff0c;可以使用以下命令&#xff1a; 对于基于Debian/Ubuntu的容器&#xff0c;使用以下命令&#xff1a; apt-get update apt-get install -y iputils-ping对于基…

【如何利用c++RAII技术实现自动加解锁】

1. 思路 可以使用C中的 RAII&#xff08;资源获取即初始化&#xff09;技术来实现自动加锁解锁。这意味着你可以创建一个包含加锁和解锁操作的类&#xff0c;并在其构造函数中加锁&#xff0c;在析构函数中解锁。这样&#xff0c;在该类的实例化对象超出作用域时&#xff0c;自…

sqlite数据库存储

Android中的SQLite是一个轻量级的数据库管理系统&#xff0c;它内置在Android系统中&#xff0c;允许开发者在应用程序中存储和管理结构化数据。下面我将详细解释如何在Android中使用SQLite进行数据库操作&#xff0c;包括创建数据库、升级数据库、添加数据、更新数据、删除数据…

分布式锁(2):基于数据库实现分布式锁

1 悲观锁 1.1 原理 在select的时候就会加锁,采用先加锁后处理的模式,虽然保证了数据处理的安全性,但也会阻塞其他线程的写操作。在读取数据时锁住那几行,其他对这几行的更新需要等到悲观锁结束时才能继续 。select ... for update 悲观锁适用于写多读少的场景,因为拿不…

Nginx在线部署和离线部署方式

Nginx 有两种安装方式: 1)在线安装的方式 1.添加Nginx 到yum源 sudo rpm -Uvh <http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm> 2.安装Nginx,直接使用yum方式 yum install -y nginx 3.启动nginx,刚安装的nginx不…