防重复提交方案
1.思路
因为服务端是使用Token来获取用户信息,没有对应的session管理机制,不能用sessionId加上用户请求的URL作为唯一标识。但是服务器中的userId是用雪花算法进行计算,能确保唯一性,所以我们选择用userId加上用户请求的URL作为用户请求的唯一标识。当用户请求POST、PUT、DELETE接口时,缓存用户的userId加上请求的地址,设置超时时间为2秒钟,当这个用户重复请求这个地址的时候,判断是否在缓存中,如果在缓存中,则进行拦截,2秒后用户才可以请求这个接口。
2.实现步骤
2.1编写aop切面对所有请求到controller层的请求进行拦截
2.1在数据库中获取要拦截的路径,为了防止多次请求数据库导致性能下降,但是又要定时的刷新缓存,把查询的数据加入缓存中,缓存的时间为1个小时。
2.2判断用户的请求是否在缓存中,如果在缓存中,则友好提示用户不能频繁操作。如果不在缓存中,则加入缓存,缓存失效时间为2秒钟。
说明:这里的缓存用的是Hazelcast,因为Hazelcast锁和超时时间不能在一行代码中执行,如果并发请求实现不了防重复提交,建议替换成Redis。
/**
* 切入controller层,对配置的地址进行判断,如果2秒内重复提交多次则进行拦截
* (拦截的地址自己进行编写)
*/
@Before("within(com.*.*.web.controller.*)")
public void before(){
// 获取请求路径
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String path = request.getServletPath();
// 截取delete风格的servletPath
if (path.contains(InsuranceConstant.APL_NO)){
path = path.substring(0, path.lastIndexOf(InsuranceConstant.APL_NO));
}
String servletPath = path;
// 查找防重复路径(查找数据库获取过来的数据,缓存在Hazelcast中,超时时间为1个小时)
SysParameter sysParameter = findDuplicationPath();
if (sysParameter == null){
log.info("没有配置防重复提交路径,退出");
return;
}
// 判断请求地址是否是防重复地址
String[] prmValue = sysParameter.getPrmValue().split(",");
long count = Arrays.stream(prmValue).filter(value -> value.equals(servletPath)).count();
if (count <= 0){
log.info("{}不是要检测路径,退出",servletPath);
return;
}
// 获取登录的用户Id
Long userId = JwtTokenUtils.decodeToken(request.getHeader(CommonConstant.TOKEN));
// 缓存Key
String uniqueKey = servletPath + userId;
// 检查缓存中是否有对应的请求地址
Object result = hazelcastUtils.get(InsuranceConstant.PREVENTING_DUPLICATION_ASPECT, uniqueKey);
if (result == null){
// 设置超时时间[2秒超时时间]
hazelcastUtils.expire(InsuranceConstant.PREVENTING_DUPLICATION_ASPECT,uniqueKey,uniqueKey
,preDuplicationMillisecond);
log.info("请求路径:{},进入防重复拦截,拦截时间:{}秒",servletPath,preDuplicationSecond);
}else {
log.info("请求路径:{},{}秒内重复提交,友好提示",servletPath,preDuplicationSecond);
throw new BusinessException(InsuranceErrorCodeEnum.REQUESTS_ARE_TOO_FREQUENT);
}
}