防重复提交是一件很普通的事,因为之前这块只拿来用了.并未关心其中的实现,现在回来记录一下.
实现方式是:前端在需要防重复提交的页面进入时去服务器拿到token,服务器要把token放到session中一份,等前端提交时删除token,第二次提交时发现token没有.就直接返回重复提交就行了.这种局限性很大,不适合微服务,微服务的话我理解需要用数据库唯一索引(不推荐)或者redis锁(推荐)来实现.
首先编写一个自定义注解RepeatToken
package com.hex.boot.hello.controller;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatToken {
boolean save() default false ;
boolean remove() default false ;
}
controller实现
@Controller
@Slf4j
public class HelloController {
/**需要获取token的接口
* @date 2019/12/13
* @param
* @return
* @author hex
*/
@RepeatToken(save = true)
@RequestMapping("/addToken")
public String add(HttpServletRequest request) {
log.info("token:"+request.getSession(true).getAttribute("token"));
return "hello";
}
/**
* 调用防止重复提交的接口
* @param request
* @param token
* @return
*/
@RepeatToken(remove = true)
@RequestMapping("/removeToken")
public String removeToken(HttpServletRequest request, @RequestParam(value = "token") String token) {
log.info("token:"+token);
return "hello";
}
}
编写token拦截器
package com.hex.boot.hello.interceptor;
import com.hex.boot.hello.controller.RepeatToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.UUID;
/**
* @Date 2019/12/13 9:22
* @Author hex
* @Desc
*/
@Slf4j
@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {
//private static final Logger LOG = LoggerF.getLogger(TokenInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("进入token拦截器!");
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
RepeatToken annotation = method.getAnnotation(RepeatToken.class);
if (annotation != null) {
boolean needSaveSession = annotation.save();
if (needSaveSession) {
request.getSession(true).setAttribute("token", UUID.randomUUID().toString());
}
boolean needRemoveSession = annotation.remove();
if (needRemoveSession) {
if (isRepeatSubmit(request)) {
log.warn("please don't repeat submit,url:" + request.getServletPath());
//如果重复提交,则重定向到列表页面
response.sendRedirect("http://www.baidu.com");
return false;
}
request.getSession(true).removeAttribute("token");
}
}
return true;
} else {
return super.preHandle(request, response, handler);
}
}
/***
*
* @param request
* @return : true:报错需要重定向 <br />
* false: 处理后续的正常业务逻辑
*/
private boolean isRepeatSubmit(HttpServletRequest request) {
String serverToken = (String) request.getSession(true).getAttribute("token");
if (serverToken == null) {
return true;
}
String clinetToken = request.getParameter("token");
if (clinetToken == null) {
return true;
}
if (!serverToken.equals(clinetToken)) {
return true;
}
return false;
}
}
对于非springboot项目直接在springmvc配置下拦截器即可,对于springboot项目需要将拦截器手动加进来:
package com.hex.boot.hello.interceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Date 2019/12/13 9:35
* @Author hex
* @Desc
*/
@Configuration
public class TokenConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册TestInterceptor拦截器
InterceptorRegistration registration = registry.addInterceptor(new TokenInterceptor());
//所有路径都被拦截
registration.addPathPatterns("/**");
//添加不拦截路径
registration.excludePathPatterns(
);
}
}