This morning, my new colleague Xiao Wang suddenly asked me: “Zhou Ge, what is idempotence?” . Then I explained to him that idempotence means that no matter how many times you perform a request, the result is the same. When we talk about idempotent we have to say double commit, you click submit button after button, theoretically it’s the same piece of data, the database should only store one piece of data, but it actually stores multiple pieces of data, and that violates idempotent. Therefore, we need to do some processing to ensure that only one piece of data can be stored in the database after clicking the submit button consecutively.

There are many ways to prevent duplicate submissions, but here’s one that I think works best.

Custom annotations +Aop implementation

We judge whether the user submits repeatedly by obtaining the USER’S IP address and the interface it accesses. If this IP address accesses the interface for many times in a period of time, we consider it as repeated submission. We can directly process the repeated submission request, and do not allow access to the target interface.

Custom annotations

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

    /** * By default, repeat commit * is counted within 1s@return* /
    long timeout(a) default 1;
}
Copy the code

Aop processing logic

We take IP + interface address as key, randomly generate UUID as value, and store it in Redis. Every time the request comes in, query redis according to the key, if it exists, it is repeated submission, throw an exception, if it does not exist, it is normal submission, the key is stored in Redis.

@Aspect
@Component
public class NoRepeatSubmitAop {

	@Autowired
	private RedisService redisUtils;

	/** * defines the pointcut */
	@Pointcut("@annotation(NoRepeatSubmit)")
	public void noRepeat(a) {}

	/** * pre-notification: notification performed before the join point *@param point
	 * @throws Throwable
	 */
	@Before("noRepeat()")
	public void before(JoinPoint point) throws Exception{
		// The request is received, and the request content is recorded
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = attributes.getRequest();
		Assert.notNull(request, "request can not null");

		// You can use the token or JSessionId
		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 the annotation
		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 the lock
			redisUtils.lSet(key, clientId, timeout);
			throw new Exception("No double submission is allowed."); }}private String getKey(String token, String path) {
		return token + path;
	}

	private String getClientId(a) {
		returnUUID.randomUUID().toString(); }}Copy the code

Provides interfaces for testing

Add our own custom annotation @noREPEATSubmit to the interface

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

    return "1";
}
Copy the code

test

We request the interface twice in a row in the browser. It is found that the first interface responds normally: 1. The second interface responds to an exception that cannot be submitted repeatedly. After 1s, click the interface again, and the normal response is found.

This is the end of the way to prevent double commits, so we have a perfect interface to prevent double commits.