SpringBoot系列19-SpringBoot防止重复请求,重复表单提交超级简单注解的实现之四(终极版II)

2023-05-16

SpringBoot防止重复请求,重复表单提交超级简单注解实现之四(终极版II)

    • 原文链接:[https://www.lskyf.com/post/213](https://www.lskyf.com/post/213)
    • 前言:
    • 防重复提交业务流程图如下
    • 1.简化DuplicateSubmitToken.java代码,只留下标记接口,新增超时设置接口
    • 2.改造DuplicateSubmitAspect.java新增超时判断代码
    • 3.TestController.java测试:包含restful请求,get请求,post请求
    • 4.浏览器测试

原文链接:https://www.lskyf.com/post/213

前言:

根据最新spring boot:2.5.0版本和在《SpringBoot防止重复请求,重复表单提交超级简单的注解实现之四(终极版I)》之上化繁为简抽取更实用的代码,新增超时机制

防重复提交业务流程图如下

在这里插入图片描述

1.简化DuplicateSubmitToken.java代码,只留下标记接口,新增超时设置接口

/**
 * @author 猿份哥
 * @description 防止表单重复提交注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DuplicateSubmitToken {
    /**
     * 保存重复提交标记 默认为需要保存
     */
    boolean save() default true;

    /**
     * 重复失效时间单位毫秒,默认5000毫秒
     * @return
     */
    long  timeOut() default 5000 ;
}

2.改造DuplicateSubmitAspect.java新增超时判断代码

/**
 * @author 猿份哥
 * @description 防止表单重复提交拦截器
 */
@Aspect
@Component
@Slf4j
public class DuplicateSubmitAspect {
    public static final String DUPLICATE_TOKEN_KEY = "duplicate_token_key";

    @Pointcut("execution(public * com.yuanfenge.springboot.duplicatesubmit.controller..*(..))")

    public void webLog() {
    }

    @Before("webLog() && @annotation(token)")
    public void before(final JoinPoint joinPoint, DuplicateSubmitToken token) throws DuplicateSubmitException {
        if (token != null) {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();

            boolean isSaveSession = token.save();
            if (isSaveSession) {
                String key = getDuplicateTokenKey(joinPoint);
                Object t = request.getSession().getAttribute(key);
                if (null == t) {
                    createKey(request, key);
                } else if (valid(t,token.timeOut())){
                    throw new DuplicateSubmitException(TextConstants.REQUEST_REPEAT);
                } else {
                    createKey(request, key);
                }
            }

        }
    }

    private void createKey(HttpServletRequest request, String key) {
        String uuid = UUIDUtil.randomUUID();
        long now = System.currentTimeMillis();
        String value = uuid + "_" + now;
        request.getSession().setAttribute(key, value);
        log.info("token-key={};token-value={}",key, value);
    }

    /**
     * 是否超时
     * @param t
     * @return
     */
    private boolean valid(Object t, long timeOut) {
        String token = t.toString();
        String[] arr = token.split("_");
        long before = Long.parseLong(arr[1]);
        long now = System.currentTimeMillis();
        if (now-before<timeOut){
            return true;
        }
        return false;
    }

    /**
     * 获取重复提交key
     * @param joinPoint
     * @return
     */
    public String getDuplicateTokenKey(JoinPoint joinPoint) {

        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.asList(joinPoint.getArgs()).stream().map(i -> String.valueOf(i)).collect(Collectors.joining());
        StringBuilder key = new StringBuilder(DUPLICATE_TOKEN_KEY);
        key.append("_").append(methodName).append(args);
        return key.toString();
    }

    /**
     * 异常
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "webLog()&& @annotation(token)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e, DuplicateSubmitToken token) {
        if (null != token
                && e instanceof DuplicateSubmitException == false) {
            //处理重复提交本身之外的异常
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            boolean isSaveSession = token.save();
            //获得方法名称
            if (isSaveSession) {
                String key = getDuplicateTokenKey(joinPoint);
                Object t = request.getSession().getAttribute(key);
                if (null != t) {
                    //方法执行完毕移除请求重复标记
                    request.getSession(false).removeAttribute(key);
                    log.info("异常情况--移除标记!");
                }
            }
        }
    }
}

3.TestController.java测试:包含restful请求,get请求,post请求


/**
 * @author 猿份哥
 * @description
 */
@RestController
public class TestController {

    @DuplicateSubmitToken
    @RequestMapping(value = "/restful/{num}", method = RequestMethod.GET)
    public Map<String, Object> restful(@PathVariable(value = "num") int num) throws Exception {
        Map<String, Object> map=new HashMap<>();
        if (num == 2) { //手动抛个异常
            throw new Exception("====system exception haha !===");
        }
        map.put("welcome","hello word !");
        map.put("method","restful:num="+num);
        return map;
    }

    @DuplicateSubmitToken
    @RequestMapping(value = "/getParam", method = RequestMethod.GET)
    public Map<String, Object> getParam(@RequestParam(value = "num") int num) throws Exception {
        Map<String, Object> map=new HashMap<>();
        if (num == 2) {
            throw new Exception("====system exception haha !===");
        }
        map.put("welcome","hello word !");
        map.put("method","get带参数:num="+num);
        return map;
    }

    @DuplicateSubmitToken
    @RequestMapping(value = "/get", method = RequestMethod.GET)
    public Map<String, Object> get() throws Exception {
        Map<String, Object> map=new HashMap<>();
        map.put("welcome","hello word !");
        map.put("method","get无参");
        return map;
    }

    /**
     * post请求方式
     * 设置30秒内不允许重复请求
     * @param num
     * @return
     * @throws Exception
     */
    @DuplicateSubmitToken(timeOut = 30*1000)
    @RequestMapping(value = "/post", method = RequestMethod.POST)
    public Map<String, Object> post(@RequestParam(value = "num") int num) throws Exception {
        Map<String, Object> map=new HashMap<>();
        map.put("welcome","hello word !");
        map.put("method","post:num="+num);
        return map;
    }
}

4.浏览器测试

http://localhost:8080/restful/1
http://localhost:8080/get?num=1
http://localhost:8080/getParam?num=1
http://localhost:8080/post
在这里插入图片描述
源码下载链接

原文链接: https://www.lskyf.com/post/213

作者:猿份哥,版权所有,欢迎保留原文链接进行转载:)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot系列19-SpringBoot防止重复请求,重复表单提交超级简单注解的实现之四(终极版II) 的相关文章

  • 【Qt线程-4】事件循环嵌套,BlockingQueuedConnection与QWaitCondition比较

    背景 xff1a 个人学习多线程控制 xff0c 写了一些博文用于记录 xff1a Qt线程 1 this xff0c volatile xff0c exec xff0c moveToThread Qt线程 2 事件循环 xff08 QCo
  • 【Qt样式(qss)-3】几套配色方案

    背景 xff1a 之前写过有关qss的博客 xff0c 记录了如何使用qt手册 xff0c 以及在项目中如何使用qss的体验 经过实践 xff0c 我归纳了自己需要的qss使用方法 xff0c 使之尽量高效 xff0c 容易维护 Qt样式
  • 【Qt样式(qss)-4】应用到QMdiArea不生效的解决

    背景 xff1a 之前写记录过几篇qss相关内容 xff1a Qt样式 xff08 qss xff09 1 手册小结 xff08 附例 xff1a 软件深色模式 xff09 Qt样式 xff08 qss xff09 2 使用小结 xff08
  • Google play billing(Google play 内支付) 下篇

    开篇 xff1a 如billing开发文档所说 xff0c 要在你的应用中实现In app Billing只需要完成以下几步就可以了 第一 把你上篇下载的AIDL文件添加到你的工程里 xff0c 第二 把 lt uses permissio
  • Qt creator中操作QAction加入QToolBar

    背景 xff1a 个人笔记 我之前没有系统化学习过任何资料 xff0c 使用很多工具都是按需出发 xff0c 直接上手 xff0c 遇到问题再研究的 所以会有一些弯路 本文言语中难免有对个人情绪的生动描述 xff0c 希望不要影响读者心情
  • Java 通过map构造树形结构

    在开发中 xff0c 经常会有将 数据组装成为树形结构的场景 xff0c 除了可以通过递归实现 xff0c 还可以通过map 组装实现 一 xff0c 构造基本数据 import apple laf JRSUIUtils import co
  • 【无标题】es搜索基本操作

    一 xff0c 准备数据 1 创建索引 PUT lagou book 2 创建mapping PUT lagou book doc mapping 34 properties 34 34 description 34 34 type 34
  • 【ES】常用操作工具

    工欲善其事 xff0c 必先利于器 xff0c es使用过程中 xff0c 有些工具能帮助我们快速的上手和使用 一 es head es head 是一款专门针对 es的客户端工具elasticSearch配置包 是一个基于node js的
  • 【es】基本概念理解

    一 xff0c 初识es 1 是什么 xff1f ElasticSearch 简称es 开源的分布式的全文搜索引擎 xff0c 可以近乎实时的存储检索数据 xff0c es使用java开发 xff0c 并且使用Lucene作为核心实现搜索功
  • 无法安装net framework 3.5 的解决方法

    电脑刚重装了Windows8 1系统 xff0c 然后安装数据库的时候 xff0c 却出现了这样的问题 xff1a 您的电脑上的应用需要使用以下windows功能 问题原因是 xff1a 在安装系统的时候 xff0c NET Framewo
  • 【计算机网络原理】第四章 数据链路层

    今天主要梳理了一下数据链路层的内容 xff0c 如下 一 宏观规划 综合数据链路层的整体 xff0c 分为两大部分 xff0c 第一部分讲解数据链路层的功能 xff0c 第二部分讲解数据链路层的功能 这些协议 xff0c 其实还是为了实现数
  • 【redis】关系型数据库 VS 非关系型数据库

    一 关系型数据库 xff1f 1 概念 关系型数据库是指采用了关系模型来组织数据的数据库 简单来说 xff0c 关系模式就是二维表格模型 主要代表 xff1a SQL Server xff0c Oracle Mysql PostgreSQL
  • resultful风格接口

    一 产生背景 网络应用程序 xff0c 越来越流行前端和后端的分离设计 当前的发展趋势是前端的设计层出不穷 比如 xff1a 各种型号的手机 平板灯其他设计 因为必须要一种统一的机制方便不同的前端和后端进行通信 这就导致了API结构的流行
  • 【kafka】Exception thrown when sending a message with key='null' and payload='lizhenjuan;99' to topic

    今天碰到一个奇怪的问题 xff0c 如下图 xff1a 一 问题 1 问题截图 上午还可以发送消息成功的 xff0c 下午突然就发送不了消息了 我就检查我代码的问题 xff0c 是传递的格式不对 xff0c 还是数据要求不对 网上的资料显示
  • 【0723】自动化运维——saltstack

    24 1 自动化运维介绍 认识自动化运维 xff1a 传统运维效率低 xff0c 大多工作人为完成传统运维工作繁琐 xff0c 容易出错传统运维每日重复做相同的事情传统运维没有标准化流程传统运维的脚本繁多 xff0c 不能方便管理自动化运维
  • 【mysql】order by多个字段排序

    今天遇到了两个字段排序的问题 xff0c 感觉不是很清晰 xff0c 所有又按照规则查询了下 xff0c 总结下 count都是306的有三个 现在需要同时按照age和count排序 xff0c 测试最后的排序结果 默认都是按照age和co
  • java8使用积累

    1 将List lt T gt 数组转换为String并用逗号隔开 String join 34 34 List 2 idea自动补全代码教程 xff1a https www cnblogs com HF Made p 11417225 h
  • 【java】手动分页工具类

    最近小编遇到一个很绕的问题 xff0c 无法使用mybatis自带的分页插件对符合条件的数据进行分页 xff0c 故收集了一个自动分页的工具类 xff1a public static lt T gt List lt T gt getPage
  • redis 使用bitMap实现统计系统在线用户数量

    BitMap xff0c 简单来说 xff0c 其实也就是 byte 数组 xff0c 用二进制表示 xff0c 一个bit的值 xff0c 或者是0 xff0c 或者是1 xff1b 也就是说一个bit能存储的最多信息是2 它用一个bit
  • xml与实体之间的转换

    在对接一些第三方接口的时候往往需要涉及到一些对xml文件的处理 xff0c 小编今天主要总结一下JavaBean与xml文件之间互相转换的探索与实例 使用JAXB技术实现xml与实体之间的转换 1 是什么 xff1a JAXB xff08

随机推荐