防止表单重复提交

思路一(前端层面):

前端页面当用户点击按钮时,时按钮置灰,不允许继续点击按钮,从而防止在延迟时间内的重复提交。
缺点:js代码容易被绕过,如进行刷新或者F12后修改置灰按钮属性。

思路二(前端层面):

前端js文件设置一个全局变量,初始值为0,点击按钮一次则将变量+1,接收到返回值时将变量-1,在调用接口前判断变量是否为1,不为1则不调用接口,相当于一个开关,0=开 1=关。

思路三(数据库层面):

数据库添加唯一约束条件,如用户名、邮箱、电话等以确保数据库只能添加一条数据。
数据库添加唯一性约束条件sql:

alter table tableName_xxx add unique key uniq_xxx(field1, field2)

缺点:虽然简单粗暴,能有效避免重复数据插入,但是无法阻止用户恶意重复提交表单,服务器大量增加插入sql语句的执行,增加数据库和服务器的负荷。

思路四(后台层面):

使用Redis和AOP自定义切入实现

  1. 自定义防止重复提交标记(@AvoidRepeatableCommit)。
  2. 对需要防止重复提交的Congtroller里的mapping方法加上该注解。
  3. 新增Aspect切入点,为@AvoidRepeatableCommit加入切入点。
  4. 每次提交表单时,Aspect都会保存当前key到reids(须设置过期时间)。
  5. 重复提交时Aspect会判断当前redis是否有该key,若有则拦截。

自定义标签:

import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;

        /**
         * 避免重复提交
         * @author lz
         * @version
         */
        @Target(ElementType.METHOD)
        @Retention(RetentionPolicy.RUNTIME)
        public @interface AvoidRepeatableCommit {
            /**
             * 指定时间内不可重复提交,单位秒
             * @return
             */
            long timeout()  default 5 ;
        }

自定义切入点Aspect:

package com.xx.web;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.xx.web.util.DynamicRedisUtil;
import com.xx.web.util.IPUtil;

/**
 * 重复提交aop
 * 
 * @author 
 * @date 
 */
@Aspect
@Component
@EnableAspectJAutoProxy(exposeProxy=true)
public class AvoidRepeatableCommitAspect {


    @Autowired 
    HttpServletRequest request; //这里可以获取到request

    /**
     * @param point
     */
    @Around("@annotation(com.xx.web.AvoidRepeatableCommit)")
    public Object around(ProceedingJoinPoint point) throws Throwable {

        String ip = IPUtil.getIpAddr(request);
        //获取注解
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        //目标类、方法
        String className = method.getDeclaringClass().getName();
        String name = method.getName();
        String ipKey = String.format("%s#%s",className,name);
        int hashCode = Math.abs(ipKey.hashCode());
        String key = String.format("%s_%d",ip,hashCode);
//        log.info("ipKey={},hashCode={},key={}",ipKey,hashCode,key);
        AvoidRepeatableCommit avoidRepeatableCommit =  method.getAnnotation(AvoidRepeatableCommit.class);
        int timeout = avoidRepeatableCommit.timeout();
        if (timeout < 0){
            timeout = 5;
        }
        //用多参数set方法保证对redis操作原子性
        Integer isSuccess = DynamicRedisUtil.setnxAndExpire(key, UUID.randomUUID().toString(), timeout*1000, DynamicRedisUtil.AVOID_REPEATABLE_COMMIT_DB);
           if (isSuccess == 0) {
               resultMap.put("errCode", 10001);
               resultMap.put("errMsg", "请勿重复提交");
               return JSON.toJSONString(resultMap);
           }
        //执行方法
        Object object = point.proceed();
        return object;
    }

}

   /**
     * redis工具类方法
     * 比setnx多了个保存失效时间
     * @author lz
     * @date 2018年8月13日 下午2:38:07
     * @param key
     * @param value
     * @param seconds 失效时间,单位秒
     * @param db
     * @return 当key不存在,保存成功并返回1,当key已存在不保存并返回0
     */
    public static Integer setnxAndExpire(final String key, String value, long milliseconds, int db) {
        JedisPool pool = getPool();
        Jedis jds = null;
        boolean broken = false;
        int setnx = 0;
        try {
            jds = pool.getResource();
            jds.select(db);
            String result = jds.set(key, value, "NX", "PX", milliseconds);
            if ("OK".equals(result)) {
                setnx = 1;
            }
            return setnx;
        } catch (Exception e) {
            broken = true;
            logger.error("setString:" + e.getMessage());
        } finally {
            if (broken) {
                pool.returnBrokenResource(jds);
            } else if (jds != null) {
                pool.returnResource(jds);
            }
        }
        return setnx;
    }

测试:

@RequestMapping(value = "testCommit",method = {RequestMethod.GET,RequestMethod.POST})
        @ResponseBody
        @AvoidRepeatableCommit(timeout = 5)
        public String testCommit(HttpServletRequest request){
            Map<String,Object> resultMap = new HashMap<String,Object>();
            try{    
                resultMap.put("success", true);
            }catch (Exception e) {
                e.printStackTrace();
                resultMap.put("success", false);
            }
            return JSON.toJSONString(resultMap);
        }

思路五(后台层面):

分布式锁详情见他人博客,推荐:

原文:https://blog.csdn.net/memmsc/article/details/80837996

最后跪求各位大佬,路过点赞~谢谢大家!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值