赞
踩
前一段时间看了jvm原理,把笔记记到了本上结果找不到了,看的内容也忘得一干二净了,后来把笔记弄成文档,结果发现也不是很方便。于实想通过csdn记录,至于有没有参考价值。。。嘿嘿。。。
最近项目经常有人反馈保存时一看出来两条,于是有了以下内容:
除前端可校验重复提交外,后台也可以
一、拦截器+注解方式:
1、自定义用于方法的注解RepeatSubmit:
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
}
2、自定义拦截器,继承HandlerInterceptorAdapter类,重写preHandle方法。在preHandle里写判重方法,通过缓存sessionId+用户编码记录每次请求的url及访问时间,校验时同查找上次请求同url的时间与当前时间比较,三秒内则视为重复提交,响应结果,preHandle方法返回false:
public class RepeatSubmitInterceptor extends HandlerInterceptorAdapter {
/**
* 请求的参数
*/
public final String REPEAT_PARAMS = "repeatParams";
/**
* 请求的时间
*/
public final String REPEAT_TIME = "repeatTime";
/**
* 缓存标识
*/
public final String CACHE_REPEAT_KEY = "repeatCache";
/**
* 时间间隔标识
*/
public static final String REPEAT_TIME_KEY = "repeatSubmit.intervalTime";
/**
* 间隔时间,单位:秒 默认10秒,两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据
* 如果需要修改此参数,修改application.yml文件中的repeatSubmit.intervalTime
*/
private static long intervalTime = Resource.getConfigToLong(REPEAT_TIME_KEY);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
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)) {
ServletUtils.renderResult(response, Global.FALSE, "不允许重复提交,请稍后再试");
return false;
}
}
return true;
} else {
return super.preHandle(request, response, handler);
}
}
/**
* @Description: 判断短时间内,是不是重复提交
*/
@SuppressWarnings({ "unchecked" })
private boolean isRepeatSubmit(HttpServletRequest request) {
HttpServletRequestWrapper repeatedlyRequest = (HttpServletRequestWrapper) request;
String sessionId = RequestContextHolder.getRequestAttributes().getSessionId();
String userCode = UserUtils.getUser().getUserCode();
String nowParams = HttpHelper.getBodyString(repeatedlyRequest);
// body参数为空,获取Parameter的数据
if (StringUtils.isEmpty(nowParams)) {
nowParams = JSONObject.toJSONString(request.getParameterMap());
}
Map<String, Object> nowDataMap = new HashMap<String, Object>(2);
nowDataMap.put(REPEAT_PARAMS, nowParams);
nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
// 请求地址(作为存放cache的key值)
String url = request.getRequestURI();
Object sessionObj = CacheUtils.get(CACHE_REPEAT_KEY, userCode+"-"+sessionId);
if (sessionObj != null) {
Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
if (sessionMap.containsKey(url)) {
Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap)) {
return true;
}
}
}
Map<String, Object> cacheMap = new HashMap<String, Object>(1);
cacheMap.put(url, nowDataMap);
CacheUtils.put(CACHE_REPEAT_KEY, userCode+"-"+sessionId, cacheMap);
return false;
}
/**
* @Description: 判断参数是否相同
*/
private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) {
String nowParams = (String) nowMap.get(REPEAT_PARAMS);
String preParams = (String) preMap.get(REPEAT_PARAMS);
return nowParams.equals(preParams);
}
/**
* @Description: 判断两次间隔时间
*/
@SuppressWarnings("static-access")
private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap) {
long time1 = (Long) nowMap.get(REPEAT_TIME);
long time2 = (Long) preMap.get(REPEAT_TIME);
if ((time1 - time2) < (this.intervalTime * 1000)) {
return true;
}
return false;
}
}
3、注册拦截器:
自定义配置类,继承WebMvcConfigurer,重写addInterceptors方法:
@EnableWebMvc
@Configuration
@ConditionalOnProperty(name="repeatSubmit.enabled", havingValue="true", matchIfMissing=true)
public class WebConfig implements WebMvcConfigurer {
/**
*
* <p>Title: addInterceptors</p>
* <p>Description: 自定义拦截规则</p>
* @param registry
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurer#addInterceptors(org.springframework.web.servlet.config.annotation.InterceptorRegistry)
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration registration = registry.addInterceptor(new RepeatSubmitInterceptor());
// 获取配置文件中,需要拦截的路径
String apps = Global.getProperty("repeatSubmit.addPathPatterns");
// 获取配置文件中,不需要拦截的路径
String epps = Global.getProperty("repeatSubmit.excludePathPatterns");
for (String uri : StringUtils.split(apps, ",")){
if (StringUtils.isNotBlank(uri)){
registration.addPathPatterns(StringUtils.trim(uri));
}
}
for (String uri : StringUtils.split(epps, ",")){
if (StringUtils.isNotBlank(uri)){
registration.excludePathPatterns(StringUtils.trim(uri));
}
}
}
}
这里对部分工具类没有详细说明,如Global.getProperty(“repeatSubmit.addPathPatterns”)获取yml文件的拦截路径,Resource.getConfigToLong(REPEAT_TIME_KEY)获取允许请求校验时间(这两个可以在测试环境内代码里写死,也可以在注解里加参数写到方法名上,校验时从注解获取即可)CacheUtils.get(CACHE_REPEAT_KEY, userCode+"-"+sessionId)缓存,可以自定义一个缓存(如Cache<String, Object> cache = CacheBuilder.newBuilder()
// 最大缓存 100 个
.maximumSize(1000)
// 设置写缓存后 5 秒钟过期
.expireAfterWrite(timeNum, TimeUnit.SECONDS)
.build()或者使用springboot自带的一级缓存)
二、AOP+注解
注解与上面基本一致,只不过通过使用aop代替拦截器,在controller方法前调用校验即可。这里用到aop的环绕通知,直接上代码:
@Aspect
@Component
public class NoRepeatSubmitAop {
private Log logger = LogFactory.getLog(getClass());
private static final int timeNum = 300;
private static final Cache<String, Object> cache = CacheBuilder.newBuilder()
// 最大缓存 100 个
.maximumSize(1000)
// 设置写缓存后 5 秒钟过期
.expireAfterWrite(timeNum, TimeUnit.SECONDS)
.build();
@Around("execution(* com.jeesite.modules..*Controller.*(..)) && @annotation(nrs)")
public Object arround(ProceedingJoinPoint pjp, NoRepeatSubmit nrs) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String sessionId = RequestContextHolder.getRequestAttributes().getSessionId();
HttpServletRequest request = attributes.getRequest();
String key = sessionId + "-" + request.getServletPath();
try {
//Object a = cache.getIfPresent(key);
if (cache.getIfPresent(key) == null) {// 如果缓存中有这个url视为重复提交
cache.put(key, 0);
Object o = pjp.proceed();
cache.invalidate(key);
return o;
} else {
return "检查到重复提交,请在" + timeNum + "秒后再提交!";
}
} catch (Throwable e) {
cache.invalidate(key);
logger.error("NoRepeatSubmitAop:验证重复提交时出现未知异常!");
logger.error(e);
e.printStackTrace();
return "请不要频繁操作!";
}
}
}
当调用业务方法pjp.proceed();抛出异常时,这里统一返回“请不要频繁操作!”提示,其实这里是不合适的,最好可以返回每个业务方法抛出的异常提示
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。