Java如何防止接口重复提交( 二 )

(2)将注解加上去之后,接下来需要自定义一个拦截器RepeatSubmitInterceptor,用于拦截并获取 加了上述这个注解的所有请求方法的相关信息,包括其请求URL和请求体数据,其核心代码如下所示:
@Componentpublic abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter{//开始拦截@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (handler instanceof HandlerMethod){HandlerMethod handlerMethod= (HandlerMethod) handler;Method method=handlerMethod.getMethod();RepeatSubmit submitAnnotation=method.getAnnotation(RepeatSubmit.class);if (submitAnnotation!=null){//如果是重复提交,则进行拦截,拒绝请求if (this.isRepeatSubmit(request)){BaseResponse subResponse=new BaseResponse(StatusCode.CanNotRepeatSubmit);CommonUtil.renderString(response,new Gson().toJson(subResponse));return false;}}return true;}else{return super.preHandle(request, response, handler);}}//自定义方法逻辑-判定是否重复提交public abstract boolean isRepeatSubmit(HttpServletRequest request);}在这里我们将其定义为抽象类,并自定义一个抽象方法:“判断当前请求是否为重复提交isRepeatSubmit()”,之所以这样做,是因为“判断是否重复提交”可以有多种实现方式,而每种实现方式可以通过继承该抽象类 并 实现该抽象方法 从而将其区分开来,某种程度降低了耦合性(面向接口/抽象类编程);如下代码所示为该抽象类的其中一种实现方式:
/** * 判断是否重复提交,整体的思路: * 获取当前请求的URL作为键Key,暂且标记为:A1,其取值为映射Map(Map里面的元素由:请求的链接url 和 请求体的数据组成) 暂且标记为V1; * 从缓存中(本地缓存或者分布式缓存)查找Key=A1的值V2,如果V2和V1的值一样,即代表当前请求是重复提交的,拒绝执行后续的请求,否则可以继续往后面执行 * 其中,设定重复提交的请求的间隔有效时间为8秒 * * 注意点:如果在有效时间内,如8秒内,一直发起同个请求url、同个请求体,那么重复提交的有效时间将会自动延长 * @author 修罗debug * @date 2020/10/21 8:12 * @link 微信:debug0868QQ:1948831260 * @blog fightjava.com */@Componentpublic class SameUrlDataRepeatInterceptor extends RepeatSubmitInterceptor{private static final String REPEAT_PARAMS = "RepeatParams";private static final String REPEAT_TIME = "RepeatTime";//防重提交keypublic static final String REPEAT_SUBMIT_KEY = "Repeat_Submit:";private static final int IntervalTime = 8;//构建本地缓存,有效时间为8秒钟private final Cache<String,String> cache= CacheBuilder.newBuilder().expireAfterWrite(IntervalTime, TimeUnit.SECONDS).build();//真正实现“是否重复提交的逻辑”@Overridepublic boolean isRepeatSubmit(HttpServletRequest request) {String currParams=HttpHelper.getBodyString(request);if (StringUtils.isBlank(currParams)){currParams=new Gson().toJson(request.getParameterMap());}//获取请求地址,充当A1String url=request.getRequestURI();//充当B1RepeatSubmitCacheDto currCacheData=https://www.isolves.com/it/cxkf/yy/JAVA/2020-10-28/new RepeatSubmitCacheDto(currParams,System.currentTimeMillis(),url);//充当键A1String cacheRepeatKey=REPEAT_SUBMIT_KEY+url;String cacheValue=cache.getIfPresent(cacheRepeatKey);//从缓存中查找A1对应的值,如果存在,说明当前请求不是第一次了.if (StringUtils.isNotBlank(cacheValue)){//充当B2RepeatSubmitCacheDto preCacheData=new Gson().fromJson(cacheValue,RepeatSubmitCacheDto.class);if (this.compareParams(currCacheData,preCacheData) && this.compareTime(currCacheData,preCacheData)){return true;}}//否则,就是第一次请求Map cacheMap = new HashMap<>();cacheMap.put(url, currCacheData);cache.put(cacheRepeatKey,new Gson().toJson(currCacheData));return false;}//比较参数private boolean compareParams(RepeatSubmitCacheDto currCacheData, RepeatSubmitCacheDto preCacheData){Boolean res=currCacheData.getRequestData().equals(preCacheData.getRequestData());return res;}//判断两次间隔时间private boolean compareTime(RepeatSubmitCacheDto currCacheData, RepeatSubmitCacheDto preCacheData){Boolean res=( (currCacheData.getCurrTime() - preCacheData.getCurrTime()) < (IntervalTime * 1000) );return res;}}该代码虽然看起来有点多,但是仔细研读,会发现其实这些代码 就是笔者在上文中贴出的实现流程图 的具体实现,可以说是将理论知识进行真正的落地实现;
在这里再重复赘述一下,其整体的实现思路为:获取当前请求的URL作为键Key,暂且标记为:A1,其取值为映射Map(Map里面的元素由:请求的链接url 、 请求体的数据、和 请求时的时间戳 三部分组成) 暂且标记为V1;从缓存中(本地缓存或者分布式缓存)查找Key=A1的值V2,如果V2和V1里的请求体数据一样 且 两次请求是在8s内,即代表当前请求是重复提交的,系统将拒绝执行后续的业务逻辑;否则可以继续往后面执行 “将用户信息插入到数据库中” 的业务逻辑;


推荐阅读