One, foreword

In some cases, due to slow network speed, user operation errors (click submit button twice in a row), page lag and other reasons, the form data may be submitted repeatedly, resulting in the database saving multiple duplicate data.

The above problems can be handed over to the front end to solve, determine how long it can not click the save button again, of course, if there are smart users can bypass the front end verification, the back end should go to intercept processing, The following editor will solve this problem by using AOP facets + custom validation annotations + Redis caching based on the SpringBoot 2.1.8.RELEASE environment.

2. Repeated submission of Spring Boot verification form

1.pom.xmlTo introduce required dependencies

<! - = = = = = = = = = = = = = = = = = = check form duplicate submissions required depend on the = = = = = = = = = = = = = = = = = = = = = -- -- > <! GroupId > <artifactId> Spring-boot-starter-AOP </artifactId> </dependency> <! -- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>Copy the code

2,application.ymlTo introduce Redis configuration

Database: 0 # redis server address host: 127.0.0.1 # redis server connection port: 6379 timeout: 6000 # Redis server connection password (default empty) # password: jedis: pool: max-active: 1000 # Connection pool maximum number of connections (use negative values to indicate no limit) max-wait: -1 # Maximum waiting time for connection pool blocking (negative value indicates no limit) max-idle: 10 # Maximum idle connection in connection pool min-idle: 5 # Minimum idle connection in connection poolCopy the code

3. Custom annotations@NoRepeatSubmit

@target (ElementType.METHOD) @retention (retentionPolicy.runtime) public @interface NoRepeatSubmit { /** * default time 3 seconds */ int time() default 3 * 1000; }Copy the code

4, AOP interception processing

Ex: In the case of single user login, the token + URL request path can be combined. If multiple users can log in at the same time, the IP address can be added

@Slf4j @Aspect @Component public class NoRepeatSubmitAop { @Autowired RedisUtil redisUtil; /** * <p> <p> ** Defines the pointcut expression: execution(public * (...)) * : Identifies any return value of a method any package + class + method (...). Arbitrary parameter * * com. Zhengqing. Demo. Modules. * API: logo AOP cutting service package name, which requires the crosscutting business class *. * Controller: identify the name of the class, class * all *. * (..) : identifies any method name, parentheses indicate parameters, and two dots indicate any parameter type * * @param PJP: pointcut object * @param noRepeatSubmit: custom annotation object * @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(); // Get the IP address, request path, token String IP = iputils.getipadrress (request); String url = request.getRequestURL().toString(); String token = request.getHeader(Constants.REQUEST_HEADERS_TOKEN); Long now = system.currentTimemillis (); String key = "REQUEST_FORM_" + IP; If (redisutil.haskey (key)) {// Long lastTime = long.parselong (redisutil.get (key)); If ((now-lasttime) > noREPEATsubmit.time ()) {// Non-repeatable submit operation - Redisutil. set(key, string.valueof (now)); Proceed (); // Proceed (); return result; } else {return APIresult. fail(" Do not re-submit!" ); Redisutil. set(key, string.valueof (now)); ApiResult result = (ApiResult) pjp.proceed(); return result; }} catch (Throwable e) {log.error({}, LLDB message ()); Return apiResult. fail(" Error while verifying form repeated submission!" ); }}}Copy the code

5. Redis tool class used

Because too many, here will not be directly posted, can refer to the end of the article given case demo source

Three, test,

Add a custom validation annotation @noREPEATSubmit to the method you want to validate

@RestController
public class IndexController extends BaseController {

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

}Copy the code

This INDEX API request is repeated here to simulate submitting a form test

First visithttp://127.0.0.1:8080/indexRefresh this request multiple times, promptingDo not submit again!

Four,

Implementation approach

  1. The first use ofAOP aspectsBefore entering the methodinterceptPerform verification logic processing of form repeated submission
  2. throughRedisKey - the value of key-value pairsStore the required logical judgment data [ex: key stores the API request path of the form submitted by the user, value stores the submission time]
  3. Logical processing

    The corresponding data is saved to Redis for the first commit

    When the submission is saved again, the time of the last submission is retrieved from redis cache to judge the current operation time.

    If the time between the current operation and the last operation is within the “time judged to be repeated submission (within 3 seconds)” set by us, the repeated submission prompt statement or other processing will be directly returned for repeated submission.

    Otherwise, it is normal to submit, enter business method processing…
supplement

If the API is strictly Restful, i.e. @postMapping is used for form submission operations, then instead of using custom annotations to determine the path to validate repeated submissions, you can intercept the request path directly on the AOP aspect. If the annotation on this method exists, it is the API for submitting the form. If it does not exist, it is the other @getMapping, @putMapping, @deletemapping operations.

This article case demo source

Gitee.com/zhengqingya…