Solve the problem of duplicate form submission

Prevent form resubmission

1, Problem background

The return function of the current management platform supports the function of uploading return excel and inserting return order records into the return order table. According to the feedback of the operation colleagues, when uploading the return excel, the return order form has duplicate return records, that is, there is the problem of repeated submission of the form.

Due to historical reasons, the refundOrderId of the return order table is not a unique index. This problem cannot be solved from the database level. The problem of repeated submission of the form can only be solved from the front and back-end code.

2, Solution

To avoid the problem of repeated form submission, when submitting the front-end form, the button can be grayed out after clicking the form submission button, that is, users are not allowed to click again. The implementation scheme is as follows:

<input type="submit" value="Submit" οnclick="javascript:{document.form.submit();this.disabled=true;}" />

Note: disabling the form submission button can only prevent users from submitting forms repeatedly at the front end, and can not prevent malicious repeated submission through the interface. A safer solution is the token solution to solve this problem.

The following describes the scheme of token implementation to avoid repeated submission of forms:

  • Before entering the form page, the backstage teacher will form a token and coexist it in the cache. Note that the token is globally unique

  • Bring this token when submitting the form

  • Check whether there is a token in the cache in the background

    1. If it exists, it means that the token is legal and not submitted repeatedly. Release and delete the token from the cache;

    2. If the token does not exist in the cache, it indicates that the form submission is illegal or repeated, and it is rejected.

During specific implementation, you can implement a special @ SubmitToken annotation for generating and verifying tokens. The code is as follows:

/**
 * Submit token
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SubmitToken {
    /**
     * Create token
     * @return
     */
    boolean create() default false;

    /**
     * Verification token
     * @return
     */
    boolean check() default false;
}

For the token annotation, intercept the annotation based on AOP and generate or verify the token. The code is as follows:

/**
 * Block duplicate form submission requests
 */
@Slf4j
@Aspect
@Service
public class NoRepeatSubmitAop {

    @Autowired
    private GeneralNearCache generalNearCache;

    public NoRepeatSubmitAop() {
        log.info("Prevent duplicate form submission aspect startup");
    }

    /**
     * Define tangent point
     */
    @Pointcut("@annotation(com.chinaums.xxx.web.mgrframework.annotation.SubmitToken)")
    public void repeatSubmitPtCut() {

    }

    /**
     * Surround notification processing
     */
    @Around("repeatSubmitPtCut()")
    public Object checkRepeatSubmit(ProceedingJoinPoint pjp) throws Throwable {

        HttpServletRequest request = null;
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            request = attributes.getRequest();
        }

        if(request != null) {

            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            SubmitToken submitTokenAnnotation = methodSignature.getMethod().getAnnotation(SubmitToken.class);

            if(submitTokenAnnotation.create()) {
                String token = UUIDGenerator.getUUID();
                request.setAttribute(WebConstant.SUBMIT_TOKEN, token);
                generalNearCache.putToCache(token, token);
            } else if(submitTokenAnnotation.check()) {

                // Direct release request
                if(StringUtils.equals(request.getMethod(), RequestMethod.GET.toString())) {
                    return pjp.proceed();
                }

                String reqToken = request.getParameter(WebConstant.SUBMIT_TOKEN);
                String token = generalNearCache.getFromCache(reqToken);

                if(StringUtils.isNotBlank(reqToken) && StringUtils.equals(reqToken, token)) {
                    generalNearCache.removeFromCache(reqToken);
                    return pjp.proceed();
                }

                throw new RepeatFormSubmitException(ResultBean.REPEAT_SUBMIT_FAIL, "Repeat form request");
            }
        }

        return pjp.proceed();
    }
}

Before jumping to the form submission page, the back-end controller adds @ SubmitToken(create = true) to generate a token annotation:

@SubmitToken(create = true)
@RequestMapping(value = "/goSubmitForm.do", method = RequestMethod.GET)
public String goSubmitForm() {
    return "submitForm";
}

The page form is submitted with a token

<input type="submit" value="Submit" οnclick="formSubmit(this);" />

Corresponding js code:

	function formSubmit(e){
		var action = document.form.action + "?submit_token="+$("#submit_token").val();
		document.form.action=action;
		document.form.submit();
		e.disabled=true;
	}

The back-end controller adds @ SubmitToken(check=true) verification token annotation:

    @RequestMapping(value = "/submitForm.do", method = RequestMethod.POST)
    @OperateLog(operType = DataConstant.OPER_TYPE_APPROVE)
    @SubmitToken(check = true)
    public void submitForm(MultipartFile uploadFile, HttpServletRequest request,
                                     HttpServletResponse response) throws Exception {

    }

Keywords: Java Front-end

Added by insane on Sun, 20 Feb 2022 16:31:28 +0200