java使用自定义注解和拦截器防止重复提交的实现详解

本文介绍了如何在Web应用中通过自定义注解和后端拦截器防止因网络延迟或用户误操作导致的重复提交问题,包括使用`@RepeatSubmit`注解、`RepeatSubmitInterceptor`拦截器以及其实现细节和SpringMVC配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Web 应用中,由于网络延迟、用户误操作等原因,可能会导致用户重复提交表单或请求。这不仅会浪费服务器资源,还可能对数据完整性造成影响。因此,我们需要一种方法来防止重复提交。

一种常见的方法是在前端使用 token 来防止重复提交,但这种方法需要在每次请求时都手动添加一个 token,比较繁琐。因此,我们可以考虑在后端实现一个拦截器来自动根据注解判断是否重复提交。

首先,我们需要定义一个 @RepeatSubmit 注解,该注解用于标注需要防止重复提交的方法。代码如下:

@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
}

接下来,我们需要实现一个抽象类 RepeatSubmitInterceptor,这个类继承自 HandlerInterceptorAdapter,用于实现拦截器的基本逻辑。代码如下:

@Component
public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter {

    // 注入 TokenService 和 RedisCache
    @Resource
    private TokenService tokenService;
    @Autowired
    private RedisCache redisCache;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // todo 刷新token信息
        String token = tokenService.getToken(request);
        if (StringUtils.isNotEmpty(token)) {
            Claims claims = tokenService.parseToken(token);
            // 解析对应的权限以及用户信息
            String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
            String userKey = tokenService.getTokenKey(uuid);
            LoginUser user = redisCache.getCacheObject(userKey);
            if (user != null) {
                tokenService.refreshToken(user);
            } else {
                throw new BaseException("登陆超时,请重新登陆");
            }
        }

        // 重复提交验证
        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)) {
                    AjaxResult ajaxResult = AjaxResult.error("不允许重复提交,请稍后再试");
                    ServletUtils.renderString(response, JSONObject.toJSONString(ajaxResult));
                    return false;
                }
            }
        }

        return super.preHandle(request, response, handler);
    }

    /**
     * 验证是否重复提交由子类实现具体的防重复提交的规则
     *
     * @return
     * @throws Exception
     */
    public abstract boolean isRepeatSubmit(HttpServletRequest request);
}

在上述代码中,我们首先注入了 TokenService RedisCache,然后在 preHandle 方法中实现了拦截器的基本逻辑。具体来说,我们首先获取请求中的 token 信息,并解析其中的用户信息和权限。如果用户信息存在且未超时,则刷新用户的 token 信息;如果用户信息不存在或已超时,则抛出异常提示用户重新登录。

接着,我们检查是否存在 RepeatSubmit 注解,如果存在则调用 isRepeatSubmit 方法来验证是否重复提交。如果是重复提交,则返回错误信息,阻止请求继续执行。

需要注意的是,在该类中,我们提供了一个抽象方法 isRepeatSubmit,用于具体的重复提交验证。这个方法需要在其子类中实现,具体的重复提交验证规则可以根据项目需求来定制。

最后,我们需要编写一个子类继承 RepeatSubmitInterceptor 并实现 isRepeatSubmit 方法。这个方法的具体实现可以根据项目需求来定制。下面是一个示例代码:

@Component
public class DefaultRepeatSubmitInterceptor extends RepeatSubmitInterceptor {

    @Autowired
    private RedisCache redisCache;

    @Override
    public boolean isRepeatSubmit(HttpServletRequest request) {
        String key = getRequestKey(request);
        if (StringUtils.isEmpty(key)) {
            return true;
        }
        boolean hasKey = redisCache.hasKey(key);
        if (hasKey) {
            return true;
        } else {
            // 若不存在key,则将其存入redis缓存中,并设置超时时间
            redisCache.setCacheObject(key, 1, 5, TimeUnit.SECONDS);
            return false;
        }
    }

    /**
     * 获取请求的唯一标识,用于防止重复提交
     *
     * @param request HTTP 请求
     * @return 请求的唯一标识
     */
    private String getRequestKey(HttpServletRequest request) {
        String token = tokenService.getToken(request);
        String url = request.getRequestURI();
        String method = request.getMethod();
        String params = JSON.toJSONString(request.getParameterMap(),
                SerializerFeature.DisableCircularReferenceDetect,
                SerializerFeature.WriteMapNullValue);
        return DigestUtils.md5Hex(token + url + method + params);
    }
}

在上述代码中,我们实现了 isRepeatSubmit 方法,具体的重复提交验证规则是:根据请求的 URL、方法类型和参数生成一个唯一标识,然后将其存入 Redis 缓存中,并设置超时时间。如果该标识已存在,则说明存在重复提交,否则说明是第一次提交。

最后,在 Spring 配置文件中配置拦截器,并将其注册到 Spring MVC 中。代码如下:

<bean id="repeatSubmitInterceptor" class="com.example.interceptor.DefaultRepeatSubmitInterceptor"/>

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter">
            <property name="preInterceptors">
                <list>
                    <ref bean="repeatSubmitInterceptor"/>
                </list>
            </property>
        </bean>
    </mvc:interceptor>
</mvc:interceptors>

在上述配置中,我们首先定义了一个 repeatSubmitInterceptor 实例,然后将其注册到 Spring MVC 的拦截器链中。

综上所述,使用自定义注解和拦截器来防止重复提交是一种比较常见的方法,通过上述步骤,我们可以方便地实现该功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沉浮yu大海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值