Spring Boot (a) Form to repeat check

I. Introduction

In some cases, due to connection speed users a mistake (continuous two clicks the submit button), the page Caton and other reasons, there may be duplication of data submission form database holds more than cause duplicate data.

For the above problems can be solved to the front end, click the Save button again it can not judge how long, of course, if there is intelligent front-end users to bypass the authentication backend should go to intercept processing, the following small series will be based on SpringBoot 2.1.8.RELEASEthe environment by AOP切面+ 自定义校验注解+ Redis缓存to solve this problem.

Two, Spring Boot check Form to repeat the operation

1, pom.xmlthe introduction of the required dependencies

<!-- ==================  校验表单重复提交所需依赖 ===================== -->
<!-- AOP依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2, application.ymlthe configuration is introduced Redis

spring:
  redis:
    # Redis数据库索引(默认为0)
    database: 0
    # Redis服务器地址
    host: 127.0.0.1
    # Redis服务器连接端口
    port: 6379
    timeout: 6000
    # Redis服务器连接密码(默认为空)
    #      password:
    jedis:
      pool:
        max-active: 1000  # 连接池最大连接数(使用负值表示没有限制)
        max-wait: -1      # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 10      # 连接池中的最大空闲连接
        min-idle: 5       # 连接池中的最小空闲连接

3, custom annotation @NoRepeatSubmit

// 作用到方法上
@Target(ElementType.METHOD)
// 运行时有效
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {
    /**
     * 默认时间3秒
     */
    int time() default 3 * 1000;
}

4, AOP intercept processing

NOTE: redis here stored keyvalues specific service by an individual flexible play, here is an example of
ex: single user can log in the case where a combination of token + url请求路径a plurality of users can simultaneously log, it can be coupled withip地址

@Slf4j
@Aspect
@Component
public class NoRepeatSubmitAop {

    @Autowired
    RedisUtil redisUtil;

    /**
     * <p> 【环绕通知】 用于拦截指定方法,判断用户表单保存操作是否属于重复提交 <p>
     *
     *      定义切入点表达式: execution(public * (…))
     *      表达式解释: execution:主体    public:可省略   *:标识方法的任意返回值  任意包+类+方法(…) 任意参数
     *
     *      com.zhengqing.demo.modules.*.api : 标识AOP所切服务的包名,即需要进行横切的业务类
     *      .*Controller : 标识类名,*即所有类
     *      .*(..) : 标识任何方法名,括号表示参数,两个点表示任何参数类型
     *
     * @param pjp:切入点对象
     * @param noRepeatSubmit:自定义的注解对象
     * @return: java.lang.Object
     */
    @Around("execution(* com.zhengqing.demo.modules.*.api.*Controller.*(..)) && @annotation(noRepeatSubmit)")
    public Object doAround(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) {
        try {
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

            // 拿到ip地址、请求路径、token
            String ip = IpUtils.getIpAdrress(request);
            String url = request.getRequestURL().toString();
            String token = request.getHeader(Constants.REQUEST_HEADERS_TOKEN);

            // 现在时间
            long now = System.currentTimeMillis();

            // 自定义key值方式
            String key = "REQUEST_FORM_" + ip;
            if (redisUtil.hasKey(key)) {
                // 上次表单提交时间
                long lastTime = Long.parseLong(redisUtil.get(key));
                // 如果现在距离上次提交时间小于设置的默认时间 则 判断为重复提交  否则 正常提交 -> 进入业务处理
                if ((now - lastTime) > noRepeatSubmit.time()) {
                    // 非重复提交操作 - 重新记录操作时间
                    redisUtil.set(key, String.valueOf(now));
                    // 进入处理业务
                    ApiResult result = (ApiResult) pjp.proceed();
                    return result;
                } else {
                    return ApiResult.fail("请勿重复提交!");
                }
            } else {
                // 这里是第一次操作
                redisUtil.set(key, String.valueOf(now));
                ApiResult result = (ApiResult) pjp.proceed();
                return result;
            }
        } catch (Throwable e) {
            log.error("校验表单重复提交时异常: {}", e.getMessage());
            return ApiResult.fail("校验表单重复提交时异常!");
        }

    }

}

5, which uses the Redis Tools

Because too many, here is not directly attached to it, and refer to the end of the case as given demo source code

Third, the test

Plus parity custom annotations on the method to be verified @NoRepeatSubmitto

@RestController
public class IndexController extends BaseController {

    @NoRepeatSubmit
    @GetMapping(value = "/index", produces = "application/json;charset=utf-8")
    public ApiResult index() {
        return ApiResult.ok("Hello World ~ ");
    }

}

Here repeat visits this indexapi request to submit the form to simulate tests

First visit http://127.0.0.1:8080/index
Here Insert Picture Description
repeatedly refresh this request, the prompt请勿重复提交!
Here Insert Picture Description

IV Summary

Realization of ideas
  1. Firstly, AOP切面before entering a method 拦截for checking logical processing Form to repeat
  2. By Redisthe key-value键值对logic judgment data storage needs [ex: key storing user submits the form to request path api, value stored commit time]
  3. 逻辑处理:
    The first data into the appropriate redis submitted to the
    extraction time of the last commit redis from the cache when the current and saved again submitted to the operation time to make a judgment,
    if the current operating time since the last operation time in the 'determination we set submitted to repeat the time (3 sec) "was repeated submitted directly returned repeatedly submit precautionary statements, or other processing,
    otherwise normal submission, the traffic entering the processing method ...
supplement

If strict compliance api Restful风格i.e., @PostMappinga form submission, annotations can not customize the way to judge the path needs to be checked repeatedly submit, intercepts the request path directly after aop section, by reflecting on the method to get the annotation if there @PostMappingif there is submission form api, ie verification process, that is, if there is no other @GetMapping, @PutMapping, @DeleteMappingoperation ...

This article Case demo source code

https://gitee.com/zhengqingya/java-workspace

Guess you like

Origin www.cnblogs.com/zhengqing/p/11943530.html