• notice
  • Congratulations on the launch of the Sought Tech site

Use Spring's AOP aspect to prevent repeated form submissions

scenes to be used

  • When submitting the form at the front end, due to network lag or misoperation, the user clicks the submit button multiple times, resulting in multiple new duplicate data.

  • The backend can use the AOP aspect to prevent repeated submissions. When the first submission is not processed, if the same data is submitted again, it will not be processed.

  • This example is relatively simple and can only prevent repeated submission of the same data at the same time; if you need to prevent the submission of different data of the same form, you need to modify the front-end code and carry a one-time form credential when submitting the form.

example code

Tools

  • HashUtilsTool class, used to calculate the value of the string MD5, the code is as follows:

import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;/**
 * @author hyx
 */public class HashUtils {
    /**
     * Get the md5 value of a string
     */
    public static String hashKeyForDisk(String key) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(key.hashCode());
        }
        return cacheKey;
    }
    private static String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }}
  • RedisLockServiceUsed to operate redislocking and unlocking, the code is as follows:

import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.script.DefaultRedisScript;import org.springframework.data.redis.core.script.RedisScript;import org.springframework.data.redis.serializer.StringRedisSerializer;import org.springframework.stereotype.Service;import java.util.Collections;/**
 * @author hyx
 */@Servicepublic class RedisLockService {
    private static final Long SUCCESS = 1L;
    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * Get the lock
         *
         * @param lockKey
         * @param value
         * @param expireTime lock valid time unit-second
         * @return
     */
    public boolean getLock(String lockKey, String value, int expireTime) {
        boolean ret = false;
        try {
            String script = "if redis.call('setNx',KEYS[1],ARGV[1]) == 1 then if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end else return 0 end";
            RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
            Object result = redisTemplate.execute(redisScript, new StringRedisSerializer(),
                    new StringRedisSerializer(), Collections.singletonList(lockKey), value, String.valueOf(expireTime));
            if (SUCCESS.equals(result)) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ret;
    }
    /**
     * release lock
     *
     * @param lockKey
     * @param value
     * @return
     */
    public boolean releaseLock(String lockKey, String value) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Object result = redisTemplate.execute(redisScript, new StringRedisSerializer(),
                new StringRedisSerializer(), Collections.singletonList(lockKey), value);
        if (SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }}

Sliced

  • Create an ASubmitannotation to be used as the entry point of the aspect, the code is as follows:

import java.lang.annotation.*;

/**
  * @author hyx
  */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ASubmit {
}
  • The aspect class implements anti-duplication submission, the code is as follows:

import com.alibaba.fastjson.JSONObject;
import com.transnal.business.api.ApiException;
import com.transnal.business.api.ReplyCode;
import com.transnal.common.spring.ApplicationContextHolder;
import com.transnal.publish.common.utils.HashUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @author hyx
 */
@Aspect
@Component
public class SubmitAspect {

    //annotation entry point
    @Pointcut("@annotation(com.transnal.publish.admin.ASubmit)")
    //method entry point
    // @Pointcut("execution(public * com.transnal.publish.admin..*Controller.create(..))")
    public void addAdvice() {
    }

    /**
     * Surround notifications
     */
    @Around("addAdvice()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        RedisLockService redisLockService = ApplicationContextHolder.getBean(RedisLockService.class);
        Object[] args = joinPoint.getArgs();
        //Calculate the md5 of the request parameter
        String md5Key = HashUtils.hashKeyForDisk(JSONObject.toJSONString(args));
        //Use the md5 of the parameter to lock before the method is executed. If md5 is repeated, it means repeated submission
        boolean lock = redisLockService.getLock(md5Key, md5Key, 30);
        if (lock) {
            try {
                // execute method
                Object proceed = joinPoint.proceed();
                return proceed;
            } catch (Exception e) {
                e.printStackTrace();
                throw new ApiException(ReplyCode.Error, e.getMessage());
            } finally {
                //Unlock if the execution succeeds or fails
                redisLockService.releaseLock(md5Key, md5Key);
            }
        }

        //Failure to lock means repeated submission
        throw new ApiException(ReplyCode.Error, "Please do not submit again");
    }
}
  • ASubmitYou can use annotations on methods that need to prevent repeated submissions. The code is as follows:

public class SlideController {
    /**
     * new interface
     */
    @ASubmit
    @Override
    public String create(Slide slide) {
        //...
    }}


Tags

Technical otaku

Sought technology together

Related Topic

0 Comments

Leave a Reply

+