springboot防重复提交

springboot防重复提交

1、场景

网页卡顿的时候,用户点击会造成重复操作

如果前端不做防重复操作。会导致重复提交,重复下单等意外操作。而且对于系统资源来说也是一种浪费

常规的解决方法是让前端把点击后的按钮设置为不可点击,这样基本上能就能解决了。99.999999%能解决。前端这么弄过后,就没有遇到过需要后端弄的了。

为了万无一失,剩下的不能解决的就需要后端做防重复点击的操作了。

2、解决方案

2.1、前端通过js让按钮点击后失效,基本上就够了。除非别人要搞你网站,直接扒你接口,放心,你的网站没有那么有价值。

2.2、数据库增加唯一索引,不建议改数据库,虽然方便。

2.3、利用令牌防止表单重复提交

表单页面初始化时,会从后端获取一个生成一个token,这个token放在表单隐藏,当表单提交时一起提交,提交后后端使该token失效;

后端判断前端提交的token为空或者失效则表单提交失败(发送token,验证token)这里需要前后端配合

2.4、使用Spring AOP自定义切入实现,作为后端,倾向于这种方式。

一般接口提交时会有token验证,表明该请求是合法一个用户。后端可以通过 用户token+类+方法来判断是否是重复请求。

RepeatSubmitTestController 文件

@RestController
@RequestMapping(path = "/repeatSubmitTest", produces = "application/json;charset=UTF-8")
public class RepeatSubmitTestController{

    @GetMapping("/listPage")
    @RepeatSubmit(timeout = 20000)
    @ApiOperation(value = "条件查询列表分页", notes = "条件查询列表分页")
    public ResponseBean listPage(@RequestParam Map<String, Object> params, HttpServletRequest request) throws MyException {
        return new ResponseBean(Constants.CODE_SUCCESS, "操作成功", "");
    }

}

RepeatSubmit 文件

// @Target 表示该注解用于什么地方
//        ElementType.CONSTRUCTOR 用在构造器
//        ElementType.FIELD 用于描述域-属性上
//        ElementType.METHOD 用在方法上
//        ElementType.TYPE 用在类或接口上
//        ElementType.PACKAGE 用于描述包
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE})

//@Retention 表示在什么级别保存该注解信息
//        RetentionPolicy.SOURCE 保留到源码上
//        RetentionPolicy.CLASS 保留到字节码上
//        RetentionPolicy.RUNTIME 保留到虚拟机运行时(最多,可通过反射获取)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {

    /**
     * 默认的间隔时间(ms),小于此时间视为重复提交
     */
    int timeout() default 2000;

}

RepeatSubmitAspect 文件

// 开启日志,需要依赖lombok
@Slf4j
// 把一个类定义为切面供容器读取
@Aspect
@Component
public class RepeatSubmitAspect {

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    // 这是一个环绕通知,它会围绕被 @RepeatSubmit 注解标记的方法执行,这里的 repeatSubmit 与下面的参数对应
    @Around("@annotation(repeatSubmit)")
    public Object around(ProceedingJoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {

        // 获取用户的token验证,这里项目用的是 header 里的  Authorization 参数
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String requestToken = request.getHeader("Authorization");

        // 获取注解
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        // 获取类,方法
        String className = method.getDeclaringClass().getName();
        String methodName = method.getName();

        // 组装key:用户唯一标识+操作类+方法
        String key = requestToken + "#" + className + "#" + methodName;
        String keyHashCode = String.valueOf(Math.abs(key.hashCode()));
        log.info("key:{},keyHashcode:{}", key, keyHashCode);

        //获取超时时间
        int timeOut = repeatSubmit.timeout();
        log.info("超时时间{}", timeOut);

        //  从缓存给中根据key获取数据
        String value = redisTemplate.opsForValue().get(keyHashCode);

        if (value != null) {
            log.info("重复提交");
            // 如果value不为空; return  "请勿重复提交";
            return new ResponseBean(Constants.CODE_SUCCESS, "重复提交,稍后重试", "");

        } else {
            log.info("首次提交");
            // value为空,则加入缓存,并设置过期过期时间
            redisTemplate.opsForValue().set(keyHashCode, "1", timeOut, TimeUnit.MILLISECONDS);
        }

        //执行Object
        Object object = point.proceed();

        return object;
    }
}

你可能感兴趣的:(springcloud从零搭建,架构,spring,boot,java,spring)