Spring boot验证重复提交

Spring boot验证重复提交

    前一段时间看了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();抛出异常时,这里统一返回“请不要频繁操作!”提示,其实这里是不合适的,最好可以返回每个业务方法抛出的异常提示

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值