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