java如何防止表单重复提交的注解@RepeatSubmit

 更新时间:2024年11月12日 09:45:11   作者:东方巴黎~Sunsiny  
@RepeatSubmit是一个自定义注解,用于防止表单重复提交,它通过AOP和拦截器模式实现,结合了线程安全和分布式环境的考虑,注解参数包括interval(间隔时间)和message(提示信息),使用时需要注意并发处理、用户体验、性能和安全性等方面,失效原因是多方面的

代码解释

@RepeatSubmit

  • 是一个自定义注解,通常用于防止表单重复提交。
  • 这个注解可以应用于控制器方法上,以确保同一个请求在一定时间内不会被多次提交。

以下是一些常见的参数和用法:

  • value:注解的名称或描述。
  • interval:两次请求之间的最小间隔时间(单位通常是毫秒)。
  • message:当检测到重复提交时返回的提示信息。

示例代码

假设有一个 @RepeatSubmit 注解的定义如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
    String value() default "";
    int interval() default 3000; // 默认3秒
    String message() default "请勿重复提交";
}

使用示例

在控制器方法中使用 @RepeatSubmit 注解:

@RestController
public class UserController {

    @PostMapping("/submitForm")
    @RepeatSubmit(interval = 5000, message = "请等待5秒后再提交")
    public ResponseEntity<String> submitForm(@RequestBody FormData formData) {
        // 处理表单提交逻辑
        return ResponseEntity.ok("表单提交成功");
    }
}

控制流图

以下是 @RepeatSubmit 注解的控制流图,展示了其工作原理:

flowchart TD
A[开始] --> B[接收请求]
B --> C{检查是否重复提交}
C -->|是| D[返回重复提交提示信息]
C -->|否| E[处理请求]
E --> F[返回成功响应]
F --> G[结束]

说明

  • A: 开始处理请求。
  • B: 接收到客户端的请求。
  • C: 检查当前请求是否与前一次请求的时间间隔小于设定的 interval。
  • D: 如果检测到重复提交,返回提示信息(如 “请等待5秒后再提交”)。
  • E: 如果没有检测到重复提交,继续处理请求。
  • F:请求处理成功后,返回成功响应。
  • G: 结束请求处理过程。

使用的设计模式

@RepeatSubmit 注解通常结合 AOP(面向切面编程) 和 拦截器模式 来实现防止表单重复提交的功能。

设计模式解析

AOP(面向切面编程):

  • 目的: 将横切关注点(如日志记录、事务管理、安全性等)从业务逻辑中分离出来,提高代码的模块化和可维护性。
  • 实现: 使用 Spring AOP 或其他 AOP 框架,通过切面(Aspect)来拦截方法调用,执行额外的逻辑(如检查重复提交)。

拦截器模式

  • 目的: 在请求到达目标方法之前或之后执行特定的逻辑。
  • 实现: 在 Spring 中,可以通过 HandlerInterceptor 或 MethodInterceptor 来实现拦截器,拦截请求并执行预处理或后处理逻辑

定义注解

   @Target(ElementType.METHOD)
   @Retention(RetentionPolicy.RUNTIME)
   public @interface RepeatSubmit {
       String value() default "";
       int interval() default 3000; // 默认3秒
       String message() default "请勿重复提交";
   }
   

创建切面

   @Aspect
   @Component
   public class RepeatSubmitAspect {

       @Around("@annotation(repeatSubmit)")
       public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable {
           // 获取方法签名
           MethodSignature signature = (MethodSignature) joinPoint.getSignature();
           Method method = signature.getMethod();

           // 获取请求上下文
           HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

           // 获取注解参数
           int interval = repeatSubmit.interval();
           String message = repeatSubmit.message();

           // 从 session 中获取上次请求的时间戳
           Long lastRequestTime = (Long) request.getSession().getAttribute(method.getName());

           // 检查是否重复提交
           if (lastRequestTime != null && System.currentTimeMillis() - lastRequestTime < interval) {
               throw new RuntimeException(message);
           }

           // 记录当前请求的时间戳
           request.getSession().setAttribute(method.getName(), System.currentTimeMillis());

           // 继续执行目标方法
           return joinPoint.proceed();
       }
   }
   

在控制器方法中使用注解

   @RestController
   public class UserController {

       @PostMapping("/submitForm")
       @RepeatSubmit(interval = 5000, message = "请等待5秒后再提交")
       public ResponseEntity<String> submitForm(@RequestBody FormData formData) {
           // 处理表单提交逻辑
           return ResponseEntity.ok("表单提交成功");
       }
   }
   

使用@RepeatSubmit需要注意什么

使用 @RepeatSubmit 注解来防止表单重复提交时,需要注意以下几个方面:

1. 注解参数配置

  • interval:设置合理的间隔时间。过短的间隔时间可能导致用户频繁遇到重复提交的提示,影响用户体验;过长的间隔时间可能无法有效防止快速连续提交。
  • message: 提供明确的提示信息,告知用户为什么请求被拒绝,帮助用户理解并采取正确的操作。

2. 并发处理

  • 线程安全: 在高并发环境下,确保时间戳的读取和写入操作是线程安全的。
  • 可以使用 ConcurrentHashMap 或 AtomicLong 等线程安全的数据结构来存储时间戳。
  • 分布式环境: 如果应用部署在多个服务器上,需要考虑如何在分布式环境中共享时间戳信息。
  • 可以使用 Redis 等分布式缓存来存储时间戳。

3. 用户体验

  • 前端提示: 在前端页面上添加防重复提交的机制,如禁用提交按钮、显示加载动画等,减少用户误操作的可能性。
  • 错误处理:提供友好的错误处理机制,当检测到重复提交时,返回清晰的错误信息,并引导用户重新尝试或联系支持人员。

4. 性能考虑

  • 性能开销: 防重复提交的检查会增加一定的性能开销,特别是在高并发场景下。确保这些检查不会成为系统性能的瓶颈。
  • 缓存策略:使用缓存来存储时间戳信息,减少对数据库或会话的频繁访问,提高性能。

5. 安全性

  • 会话管理: 确保会话管理的安全性,防止会话劫持等攻击。
  • 时间戳验证: 验证时间戳的有效性和合法性,防止恶意用户篡改时间戳。

6. 日志记录

  • 日志记录: 记录每次请求的时间戳和处理结果,便于后续的审计和问题排查。

示例代码

以下是一个更完善的 @RepeatSubmit 注解和切面实现,考虑了上述注意事项:

定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
    String value() default "";
    int interval() default 3000; // 默认3秒
    String message() default "请勿重复提交";
}
// 创建切面
@Aspect
@Component
public class RepeatSubmitAspect {

    @Autowired
    private RedisTemplate<String, Long> redisTemplate;

    @Around("@annotation(repeatSubmit)")
    public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable {
        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 获取请求上下文
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        // 获取注解参数
        int interval = repeatSubmit.interval();
        String message = repeatSubmit.message();

        // 生成唯一的请求标识
        String key = method.getName() + ":" + request.getRemoteAddr();

        // 从 Redis 中获取上次请求的时间戳
        Long lastRequestTime = redisTemplate.opsForValue().get(key);

        // 检查是否重复提交
        if (lastRequestTime != null && System.currentTimeMillis() - lastRequestTime < interval) {
            throw new RuntimeException(message);
        }

        // 记录当前请求的时间戳
        redisTemplate.opsForValue().set(key, System.currentTimeMillis(), interval, TimeUnit.MILLISECONDS);

        // 继续执行目标方法
        return joinPoint.proceed();
    }
}
// 在控制器方法中使用注解
@RestController
public class UserController {

    @PostMapping("/submitForm")
    @RepeatSubmit(interval = 5000, message = "请等待5秒后再提交")
    public ResponseEntity<String> submitForm(@RequestBody FormData formData) {
        // 处理表单提交逻辑
        return ResponseEntity.ok("表单提交成功");
    }
}

控制流图

以下是 @RepeatSubmit 注解的控制流图,展示了其工作原理:

flowchart TD
    A[开始] --> B[接收请求]
    B --> C[生成唯一请求标识]
    C --> D[从 Redis 获取上次请求时间戳]
    D -->|存在且未过期| E[返回重复提交提示信息]
    D -->|不存在或已过期| F[记录当前请求时间戳]
    F --> G[继续执行目标方法]
    G --> H[返回成功响应]
    H --> I[结束]

说明

  • A: 开始处理请求。
  • B: 接收到客户端的请求。
  • C: 生成唯一的请求标识,通常包括方法名和客户端 IP 地址。
  • D: 从 Redis 中获取上次请求的时间戳。
  • E: 如果存在且未过期,返回重复提交提示信息。
  • F: 如果不存在或已过期,记录当前请求的时间戳。
  • G: 继续执行目标方法。
  • H: 请求处理成功后,返回成功响应。
  • I: 结束请求处理过程。

使用@RepeatSubmit会失效吗

使用 @RepeatSubmit 注解来防止表单重复提交时,确实可能会遇到一些情况下失效的问题。

以下是一些常见的失效原因及解决方案:

1. 前端快速连续点击

  • 原因: 用户在短时间内快速连续点击提交按钮,导致后端无法及时响应和处理。
  • 解决方案: 前端禁用按钮:
  • 在用户点击提交按钮后,立即禁用按钮,防止多次点击。 前端显示加载动画: 显示加载动画,告知用户请求正在处理中。

2. 网络延迟

  • 原因: 网络延迟可能导致用户认为请求失败,从而再次提交。
  • 解决方案: 前端超时提示: 设置合理的请求超时时间,并在超时后提示用户。
  • 后端重试机制: 在后端实现重试机制,但需谨慎处理,避免无限重试。

3. 会话失效

  • 原因: 如果使用会话(Session)来存储时间戳,会话可能因超时或服务器重启而失效。
  • 解决方案: 使用分布式缓存: 使用 Redis等分布式缓存来存储时间戳,确保在多服务器环境下也能正常工作。

4. 并发请求

  • 原因: 在高并发环境下,多个请求可能同时到达,导致时间戳检查失效。
  • 解决方案: 线程安全: 使用线程安全的数据结构(如ConcurrentHashMap 或 AtomicLong)来存储时间戳。
  • 分布式锁: 在分布式环境下,使用分布式锁(如 Redis分布式锁)来确保时间戳的读取和写入操作是原子性的。

5. 时间戳精度问题

  • 原因: 时间戳的精度可能不够高,导致短时间内多次请求被视为同一请求。
  • 解决方案: 提高时间戳精度:使用更高精度的时间戳(如纳秒)来减少冲突。

6. 代码逻辑错误

  • 原因: 切面或拦截器的逻辑错误可能导致 @RepeatSubmit 注解失效。
  • 解决方案: 代码审查:仔细审查切面或拦截器的代码,确保逻辑正确。
  • 单元测试: 编写单元测试,覆盖各种边界情况,确保 @RepeatSubmit 注解按预期工作。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java中String.intern()方法功能介绍

    java中String.intern()方法功能介绍

    这篇文章主要介绍了java中String.intern()方法具有什么功能,主要包括String.intern原理,JDK6中String.intern()的相关知识,本文给大家介绍的非常详细,需要的朋友可以参考下
    2022-06-06
  • SpringBoot实现读取YML,yaml,properties文件

    SpringBoot实现读取YML,yaml,properties文件

    yml,yaml,properties三种文件都是用来存放配置的文件,一些静态数据,配置的数据都会存放到里边。本文主要为大家整理了SpringBoot实现读取YML,yaml,properties文件的方法,需要的可以参考一下
    2023-04-04
  • Spring Boot中@Autowired注入为空的原因以及解决方法

    Spring Boot中@Autowired注入为空的原因以及解决方法

    最近在开发中遇到了使用@Autowired注解自动装配时会报空指针,发现对象并没有装配进来,下面这篇文章主要给大家介绍了关于Spring Boot中@Autowired注入为空的原因以及解决方法,需要的朋友可以参考下
    2024-01-01
  • Java从网络读取图片并保存至本地实例

    Java从网络读取图片并保存至本地实例

    这篇文章主要为大家详细介绍了Java从网络读取图片并保存至本地的实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • Spring Cloud Hystrix的基本用法大全

    Spring Cloud Hystrix的基本用法大全

    这篇文章主要介绍了Spring Cloud Hyxtrix的基本使用,它是Spring Cloud中集成的一个组件,在整个生态中主要为我们提供服务隔离,服务熔断,服务降级功能,本文给大家介绍的非常详细,需要的朋友可以参考下
    2022-07-07
  • Javafx利用fxml变换场景的实现示例

    Javafx利用fxml变换场景的实现示例

    本文主要介绍了Javafx利用fxml变换场景的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-07-07
  • 解决MyBatis中为类配置别名,列名与属性名不对应的问题

    解决MyBatis中为类配置别名,列名与属性名不对应的问题

    这篇文章主要介绍了解决MyBatis中为类配置别名,列名与属性名不对应的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • Spring Validation接口入参校验示例代码

    Spring Validation接口入参校验示例代码

    Spring Validation是一种用于实现数据校验的框架,它提供了一系列的校验器,针对不同的数据类型可以使用不同的校验器进行校验,下面这篇文章主要给大家介绍了关于Spring Validation接口入参校验的相关资料,需要的朋友可以参考下
    2023-06-06
  • java环境变量配置超详细图文教程

    java环境变量配置超详细图文教程

    在我们学习Java语言的时候,要在命令提示符里运用Java和Javac,用到这两个命令的时候就要配置Java环节变量才可以,这篇文章主要给大家介绍了关于java环境变量配置的相关资料,需要的朋友可以参考下
    2023-10-10
  • Java并发程序入门介绍

    Java并发程序入门介绍

    这篇文章主要介绍了Java并发程序入门 ,需要的朋友可以参考下
    2015-03-03

最新评论