天津建设厅 注册中心网站首页,如何做商业网站分析,aspcms开源企业网站建设系统,环球购物官方网站防止表单重复提交的几种方式#xff0c;演示一个自定义注解方式的实现 一、防止表单重复提交的几种方式方式一#xff1a;Token 机制方式二#xff1a;去重表#xff08;主要是利用 MySQL 的唯一索引机制来实现的#xff09;方式三#xff1a;Redis 的 setnx方式四#… 防止表单重复提交的几种方式演示一个自定义注解方式的实现 一、防止表单重复提交的几种方式方式一Token 机制方式二去重表主要是利用 MySQL 的唯一索引机制来实现的方式三Redis 的 setnx方式四设置状态字段方式五锁机制方式六自定义注解 二、自定义注解方式的实现1. 准备工作解决请求参数为JSON时采用IO流读取只能请求一次的问题2. 封住一个RedisCache简化使用3. 完善RepeatSubmitInterceptor拦截器解析注解判断是否重复提交4. 测试注解 一、防止表单重复提交的几种方式
方式一Token 机制
客户端请求服务端获取一个 token每一次请求都获取到一个全新的 token token 会有一个超时时间将 token 存入 redis 中然后将 token 返回给客户端。客户端将来携带刚刚返回的 token 去请求一个接口。服务端收到请求后分为两种情况 如果 token 在 redis 中直接删除该 token然后继续处理业务请求。如果 token 不在 redis 中说明 token 过期或者当前业务已经执行过了那么此时就不执行业务逻辑。 特点实现简单但是多了一个获取 token 的过程。
方式二去重表主要是利用 MySQL 的唯一索引机制来实现的
客户端请求服务端服务端将这次的请求信息请求地址、参数等存入到一个 MySQL 去重表中这个去重表要根据这次请求的某个特殊字段建立唯一索引或者主键索引。判断是否插入成功 成功继续完成业务功能。失败表示业务已经执行过了这次就不执行业务了。 问题MySQL 的容错性会影响业务、高并发环境可能效率低。
方式三Redis 的 setnx
客户端请求服务端服务端将能代表本次请求唯一性的业务字段通过 setnx 的方式存入 redis并设置超时时间。判断 setnx 是否成功 成功继续处理业务。失败表示业务已经执行过了。
方式四设置状态字段
给要处理的数据设置一个状态字段。
方式五锁机制
乐观锁数据库中增加版本号字段每次更新都根据版本号来判断。更新之前先去查询要更新记录的版本号第二步更新的时候将版本号也作为查询条件。
select version from xxx where id xxx;
update xxx set xxxxxx where xxxxxx and versionxxx;悲观锁 假设每一次拿数据都会被修改所以直接上排他锁就行了。
start;
select * from xxx where xxx for update;
update xxx
commit;方式六自定义注解
将当前请求的地址参数缓存起来下次再来一个请求时去判断和缓存中的请求是否完全一样一样的话并且小于规定的时间间隔则认为是重复提交。
二、自定义注解方式的实现
1. 准备工作解决请求参数为JSON时采用IO流读取只能请求一次的问题
我们现在要使用拦截器拦截请求缓存的地址参数等信息但是如果请求参数为JSON拦截器拦截之后接口就无法再一次获取了所以先要解决这个问题。前边文章已经写过点击跳转如何解决请求参数为JSON时采用IO流读取只能请求一次的问题
2. 封住一个RedisCache简化使用
Component
public class RedisCache {AutowiredRedisTemplate redisTemplate;public T void setCacheObject(final String key, final T value, Integer timeout, final TimeUnit timeUnit) {redisTemplate.opsForValue().set(key, value, timeout, timeUnit);}public T T getCacheObject(final String key) {ValueOperationsString,T valueOperations redisTemplate.opsForValue();return valueOperations.get(key);}
}3. 完善RepeatSubmitInterceptor拦截器解析注解判断是否重复提交
详细步骤见代码注释
Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {public static final String REPEAT_PARAMS repeat_params;public static final String REPEAT_TIME repeat_time;public static final String REPEAT_SUBMIT_KEY repeat_submit_key;public static final String HEADER Authorization;AutowiredRedisCache redisCache;Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//所以的controller方法都会被封装成HandlerMethodif (handler instanceof HandlerMethod){//分析注解HandlerMethod handlerMethod (HandlerMethod) handler;Method method handlerMethod.getMethod();RepeatSubmit repeatSubmit method.getAnnotation(RepeatSubmit.class);//如果注解存在请求重复if (repeatSubmit ! null){if (isRepeatSubmit(request,repeatSubmit)){//拦截返回错误信息HashMapString, Object map new HashMap();map.put(status,500);map.put(message,repeatSubmit.message());response.setContentType(application/json;charsetutf-8);response.getWriter().write(new ObjectMapper().writeValueAsString(map));return false;}}}return true;}private boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit repeatSubmit) {//获取请求参数字符串String nowParams ;//RepeatableReadRequestWrapper 说明是JSON格式if (request instanceof RepeatableReadRequestWrapper){try {nowParams ((RepeatableReadRequestWrapper) request).getReader().readLine();} catch (IOException e) {e.printStackTrace();}}//否则说明参数是key-value格式if (StringUtils.isEmpty(nowParams)){try {nowParams new ObjectMapper().writeValueAsString(request.getParameterMap());} catch (JsonProcessingException e) {e.printStackTrace();}}//包装参数和当前时间HashMapString, Object nowDataMap new HashMap();nowDataMap.put(REPEAT_PARAMS,nowParams);nowDataMap.put(REPEAT_TIME,System.currentTimeMillis());//获取请求信息组装keyString requestURI request.getRequestURI();String header request.getHeader(HEADER);String cacheKey REPEAT_SUBMIT_KEY requestURI header.replace(Bearer ,);//根据key查找redisObject cacheObject redisCache.getCacheObject(cacheKey);if (cacheObject ! null){//这里说明不是第一次判断是否为重复请求参数、时间MapString, Object cacheMap (MapString, Object) cacheObject;if (compareParams(cacheMap, nowDataMap) compareTime(cacheMap, nowDataMap, repeatSubmit.interval())){return true;}}//到这里说明是第一次访问redisCache.setCacheObject(cacheKey,nowDataMap,repeatSubmit.interval(), TimeUnit.MILLISECONDS);return false;}private boolean compareTime(MapString, Object cacheMap, HashMapString, Object nowDataMap, int interval) {Long nowTime (Long) nowDataMap.get(REPEAT_TIME);Long cacheTime (Long) cacheMap.get(REPEAT_TIME);if (nowTime - cacheTime interval) {return true;}return false;}private boolean compareParams(MapString, Object cacheMap, HashMapString, Object nowDataMap) {String cacheParams (String) cacheMap.get(REPEAT_PARAMS);String nowParams (String) nowDataMap.get(REPEAT_PARAMS);return nowParams.equals(cacheParams);}}4. 测试注解 到这里一个自定义注解方式的防重就实现完了点击跳转源码仓库地址。