Duplicate submissions, how do you handle them?

This morning, a new colleague, Xiao Wang, suddenly asked me, "What is idempotency, Zhou Ge?"Then I explained to him that idempotency means that no matter how many requests you make, the result will be the same.When it comes to idempotency, you have to repeat the submission button. In theory, this is the same data. The database should only store one, but actually there are many, which violates idempotency.So we need to do something to make sure that the database can only store one piece of data when we click the Submit button continuously.

There are many ways to prevent duplicate submissions, so let me just say one that I think works better.

Custom Annotation+Aop Implementation

We determine whether a user submits a duplicate request by acquiring the user's ip and the interface he accesses. If the ip accesses this interface multiple times over a period of time, we consider it a duplicate submission. We will process the duplicate submission request directly and do not allow access to the target interface.

Custom Notes

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NoRepeatSubmit {

    /**
     * Duplicate submissions within default 1s minutes
     * @return
     */
    long timeout() default 1;
}

Aop Processing Logic

We use the ip+interface address as the key, randomly generate a UUID as the value, and store it in redis.Each request comes in, queries redis according to the key, and if it exists it means a duplicate submission, throws an exception, if it does not exist it is a normal submission, and stores the key in redis.

@Aspect
@Component
public class NoRepeatSubmitAop {

	@Autowired
	private RedisService redisUtils;

	/**
	 * 	Define entry points
	 */
	@Pointcut("@annotation(NoRepeatSubmit)")
	public void noRepeat() {}

	/**
	 * 	Pre-notification: Notification performed before the connection point
	 * @param point
	 * @throws Throwable
	 */
	@Before("noRepeat()")
	public void before(JoinPoint point) throws Exception{
		// Receive request, record request content
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = attributes.getRequest();
		Assert.notNull(request, "request can not null");

		// You can use token or JSessionId here
		String token = IpUtils.getIpAddr(request);
		String path = request.getServletPath();
		String key = getKey(token, path);
		String clientId = getClientId();
		List<Object> lGet = redisUtils.lGet(key, 0, -1);
		// Get annotations
		MethodSignature signature = (MethodSignature) point.getSignature();
		Method method = signature.getMethod();
		NoRepeatSubmit annotation = method.getAnnotation(NoRepeatSubmit.class);
		long timeout = annotation.timeout();
		boolean isSuccess = false;
		if (lGet.size()==0 || lGet == null) {
			isSuccess = redisUtils.lSet(key, clientId, timeout);
		}
		if (!isSuccess) {
			// Failed to acquire lock, considered a duplicate request
			redisUtils.lSet(key, clientId, timeout);
			throw new Exception("No duplicate submissions are allowed");
		}

	}

	private String getKey(String token, String path) {
		return token + path;
	}

	private String getClientId() {
		return UUID.randomUUID().toString();
	}
}

Provide interfaces for testing

Add our custom comment @NoRepeatSubmit to the interface

@RequestMapping("/test")
@NoRepeatSubmit
public String tt(HttpServletRequest request) {

    return "1";
}

test

We request interfaces in the browser twice in a row.The first interface responded to normal content: 1. The second interface responded to exceptional information that could not be submitted repeatedly.1s and then click on the interface, found that it responded to normal content.

So far, this way of preventing duplicate submissions has been described, and we have perfectly prevented interface duplicate submissions.

Tags: Programming Redis Database

Posted on Tue, 23 Jun 2020 21:12:08 -0400 by mania