防止重复提交插件

一、原理

采用在session中放入token的方式来进行验证,在每次去页面是将token传入页面,页面相关业务提交时将传入页面的token提交,后台接收后与session中的token进行比对,如果相同就进行后面的业务。不同就返回提示到页面。

二、使用范围

起始理论上java web项目都可以用,不过不同项目可能会有版本jar冲突,本插件是基于springboot2.0.6开发的。

三、使用方式

1.引入插件(先把插件发布到本地仓库或者你的私服)

<dependency>
	<groupId>com.sly</groupId>
	<artifactId>plugin-anti-duplicate-commit</artifactId>
	<version>1.0</version>
</dependency>

 2.配置属性文件

anti-duplicate-commit: 
  message: 页面超时请刷新重试!

3.开启反重复提交

在启动类上加入注解

@EnableAntiDuplicateCommit

4.为需要验证的Controller方法加入注解

@AntiDuplicateCommit(keys = { DemoToken.DEMO_ADD_TOKEN }, isCheckToken = false, isReturnToken = true)

实际代码例子如下:

@RequestMapping("/toAdd")
@AntiDuplicateCommit(keys = { DemoToken.DEMO_ADD_TOKEN }, isCheckToken = false, isReturnToken = true)
public String toAdd(HttpServletRequest request, HttpServletResponse response) {
	return "/pages/add.html";
}

@ResponseBody
@RequestMapping("/demoAddSubmit")
@AntiDuplicateCommit(keys = { DemoToken.DEMO_ADD_TOKEN }, isCheckToken = true, isReturnToken = false)
public Object demoAddSubmit(HttpServletRequest request, HttpServletResponse response) {
	Map<String, Object> result = new HashMap<>(16);
	try {
		System.out.println("我是新增业务方法,我执行了!");
		result.put("status", 200);
		result.put("message", "新增成功!");
	} catch (Exception e) {
		LOGGER.error(ExceptionUtils.getStackTrace(e));
		result.put("status", 400);
		result.put("message", "新增失败!");
	}
	return result;
}

四、注解说明

keys:token的key,可以是多个。

isCheckToken:是否验证token。

isReturnToken:是否向页面或返回对象中返回新的token。

 五、注意事项

1. 返回对象

使用该插件返回对象必须统一。这里是针对返回对象都是map写的,当然了实际使用中肯定不一样,比如我自己个人的项目都是使用的一个自己封装的返回对象,所以我自己项目中该插件设置返回值的部分就和这个例子不一样。

例如demo中返回数据对象都是map。跳转页面时返回都是String,值采用request域返回。

2. 提交数据

提交数据时token的字段必须与注解上的字段一致,不然切面验证时无法取到值。

<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title>新增</title>
	</head>
	<script>
		var webRoot = '[[${#httpServletRequest.getContextPath()}]]';
	</script>
	<body>
		<input type="hidden" id="DEMO_ADD_TOKEN" name="DEMO_ADD_TOKEN" th:value="${DEMO_ADD_TOKEN}" />
		<button onclick="add();">新增</button>
	</body>
	<script th:src="@{/resource/common.js}"></script>
	<script th:src="@{/resource/jquery.min.js}"></script>
	<script>
		function add(){
			$.ajax({
				type: "post",
				url: webRoot + "/demo/demoAddSubmit",
				dataType: "json",
				data: {
					DEMO_ADD_TOKEN: $.trim($("#DEMO_ADD_TOKEN").val())
				},
				success: function(data) {
					if(data.status == 200) {
						alert(data.message);
					} else {
						alert(data.message);
					}
				}
			});
		}
	</script>
</html>

3. 如何在我的项目使用

这个demo使用的都是map进行返回数据封装,跳转页面都是String。如果需要适合自己的项目,需要对切面进行修改。这个项目并不是一个产品,只是一个技术的验证而已,可以看到有些地方并不是那么尽善尽美,最终使用时还是要和具体的规范结合。

AntiDuplicateCommitAspect.java 根据需要自己改吧,毕竟每个项目都有自己的规范,有些东西没法用一套代码适应所有。当然提供一个接口让用户自己实现似乎也可以,不过使用起来会麻烦一些。再说了一个公司或者一个项目这些东西都没有统一起来,那只能说明管理者的失职,以及参与者的不专业。

package com.sly.plugin.antiduplicatecommit.aop;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.sly.plugin.antiduplicatecommit.annotation.AntiDuplicateCommit;
import com.sly.plugin.antiduplicatecommit.properties.AntiDuplicateCommitProperties;

/**
 * 反重复提交AOP
 * 
 * @author sly
 * @time 2019年5月15日
 */
@Aspect
public class AntiDuplicateCommitAspect {
	private static final Logger LOGGER = LoggerFactory.getLogger(AntiDuplicateCommitAspect.class);

	private AntiDuplicateCommitProperties antiDuplicateCommitProperties;

	public void setAntiDuplicateCommitProperties(AntiDuplicateCommitProperties antiDuplicateCommitProperties) {
		this.antiDuplicateCommitProperties = antiDuplicateCommitProperties;
	}

	@Around("@annotation(com.sly.plugin.antiduplicatecommit.annotation.AntiDuplicateCommit)")
	public Object around(ProceedingJoinPoint point) throws Throwable {

		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
				.getRequest();

		// 判断请求类型
		String header = request.getHeader("X-Requested-With");
		boolean isAjax = "XMLHttpRequest".equals(header) ? true : false;

		// 获取注解
		MethodSignature signature = (MethodSignature) point.getSignature();
		Method method = signature.getMethod();
		// 获取方法上的注解对象
		AntiDuplicateCommit antiDuplicateCommit = method.getAnnotation(AntiDuplicateCommit.class);

		// 返回结果map
		Map<String, Object> result = new HashMap<>(16);

		// 是否验证通过
		boolean isCheckPassed = true;

		// 获取注解参数
		String[] keys = antiDuplicateCommit.keys();
		if (antiDuplicateCommit.isCheckToken()) {
			for (String key : keys) {
				String token = request.getParameter(key);
				String existToken = (String) request.getSession().getAttribute(key);
				//移除旧token 避免被重复使用
				request.getSession().removeAttribute(key);
				if (StringUtils.isBlank(existToken) || !existToken.equals(token)) {
					result.put("message", antiDuplicateCommitProperties.getMessage());
					isCheckPassed = false;
					break;
				}
			}
		}

		// 回写token
		if (antiDuplicateCommit.isReturnToken()) {
			// 如果需要回传token,那么根据key值重新设置token
			for (String key : keys) {
				String uuid = UUID.randomUUID().toString();
				request.getSession().setAttribute(key, uuid);
				request.setAttribute(key, uuid);
				// 为返回结果设置token,便于ajax请求重新设置页面token
				result.put(key, uuid);
			}
		}

		if (!isCheckPassed) {
			return result;
		}

		// 执行方法
		try {
			Object ret = point.proceed();
			if (isAjax && antiDuplicateCommit.isReturnToken()) {
				// ajax请求需要给返回结果设置新token
				@SuppressWarnings("unchecked")
				Map<String, Object> originResultMap = (Map<String, Object>) ret;
				for (String key : keys) {
					originResultMap.put(key, request.getSession().getAttribute(key));
				}
				return originResultMap;
			}
			return ret;
		} catch (Exception e) {
			LOGGER.error("原始异常:" + ExceptionUtils.getStackTrace(e));
			return result;
		}

	}
}

插件GitHub地址:https://github.com/SLY1311220942/plugin-anti-duplicate-commit

插件演示demoGitHub地址:https://github.com/SLY1311220942/demo-antiduplicatecommit

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值