Spring boot AOP 同步锁 防止表单重复提交(严格测试)

Spring boot AOP 同步锁 防止表单重复提交(严格测试)

防止表单重复提交,一直是软件架构的基础,网上的思路经严格测试, 多不靠谱, 现提供两种思路。
单块系统:利用Session机制实现。
分布式系统:利用redis缓存机制实现。

先上图看看效果

不上锁.效果截图 , 7次 请求 6次 成功.
同步锁防止表单重复提交
上锁效果截图, 7次 请求只有 1次 成功.
同步锁防止表单重复提交

单块系统

实现思路:
表单加载后,通过ajax获取token,设置到session,并填写到表单hidden token中
表单提交时,携带token参数
利用aop拦截器校验是否携带此token
携带token是否与session中token值相同,不同抛异常,相同从session移除.

1.pom.xml导入AOP

<!-- aop -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.编写注解 TokenCheck

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 避免重复提交
 * 
 * @author 冯晓东
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TokenCheck {

}

3.定义@Aspect拦截器

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.fxd.cachetest.util.WebUtil;

/**
 * 重复提交aop
 * 
 * @author 冯晓东
 */
@Aspect
@Component
public class TokenAspect {

	private static final Logger logger = LoggerFactory.getLogger(TokenAspect.class);

    /**
	 * @param jp
	 * 
	 *           经测试,会按照单个浏览器,并行执行. 无需担心同步问题.
	 */
	@Before("@annotation(com.fxd.cachetest.base.token.TokenCheck)")
	public void before(JoinPoint jp) throws Throwable {
		String token = WebUtil.getRequest().getParameter("token");
		logger.info("token --> {}", token);
		if (token == null || "".equals(token.trim())) {
			throw new RuntimeException("500:Parameter <token> can not be null.");
		}
		if (!token.contains("@@")) {
			throw new RuntimeException("500:Parameter <token> is invalid key.");
		}

		String key = token.split("@@")[0];
		String snToken = WebUtil.getAsyncToken("token@@" + key);
		
		logger.info("session token --> {}", snToken);
		if (!token.equals(snToken)) {
			throw new RuntimeException("500:Token is invalid.");
		}

	}
}

4.页面表单加载前,通过 /get_token 获取token到隐藏域 “token”

/**
	 * 
	 * @param key
	 * @return
	 */
	@GetMapping("/get_token")
	public String get_token(String key) {
		if (key == null || "".equals(key.trim())) {
			throw new RuntimeException("500:Parameter <key> can not be null.");
		}

		// 设置token值
		String token = key + "@@" + UUID.randomUUID();
		WebUtil.getSession().setAttribute("token@@" + key, token);
		return token;
	}

<input type='hidden' name='token' />

5.提交表单时,加入@TokenCheck注解

@RestController
public class WebTest {

	@RequestMapping("/test")
	@TokenCheck
	public String test() {

		return new Timestamp(System.currentTimeMillis()).toString();
	}
}

分布式系统

思路是相同的,只是将值存储在redis中(设置合理时间)
通过 redisTemplate代替session实现.这里不再赘述.

详细代码可私聊我获取.

发布了8 篇原创文章 · 获赞 157 · 访问量 7176

猜你喜欢

转载自blog.csdn.net/u014745631/article/details/104090024